@@ -244,7 +244,7 @@ def __iter__(self):
244244 if not k .startswith ("_" ):
245245 yield k , v
246246
247- def _sql (self ):
247+ def _columns_values (self ):
248248 columns = list ()
249249 values = list ()
250250 for k , v in vars (self ).items ():
@@ -253,8 +253,11 @@ def _sql(self):
253253 values .append (v )
254254 return columns , values
255255
256+ def _values (self ):
257+ return list (v for k , v in vars (self ).items () if not k .startswith ("_" ))
258+
256259 def _select (self ):
257- columns , values = self ._sql ()
260+ columns , values = self ._columns_values ()
258261 sql = " and " .join (f"`{ column } `=?" for column in columns )
259262 return sql , values
260263
@@ -346,24 +349,30 @@ def get_cache(cache_file: Path = None):
346349 return cache
347350
348351
349- def check_cache (key : CacheKey , file_timestamp : Path , table : str , columns : t .Iterable [str ], no_age_check = False ):
352+ def check_cache (
353+ key : CacheKey , timestamp_file : Path , table : str , columns : t .Iterable [str ], no_age_check = False , having = None
354+ ):
350355 cache = get_cache ()
351356 db = cache ["db" ]
352357 db .row_factory = sqlite3 .Row
353358
354- where , key_values = key ._select ()
355- cursor = db .execute (f"select * from `{ table } ` where { where } " , key_values )
359+ key_columns , key_values = key ._columns_values ()
360+ where = " and " .join (f"`{ column } `=?" for column in key_columns )
361+ sql = f"select * from `{ table } ` where { where } "
362+
363+ if having :
364+ sql += " group by " + "," .join (f"`{ column } `" for column in key_columns )
365+ sql += " having " + having
366+
367+ cursor = db .execute (sql , key_values )
368+
356369 row = cursor .fetchone ()
357370 if row :
358- timestamp = file_timestamp .stat ().st_mtime_ns
371+ timestamp = timestamp_file .stat ().st_mtime_ns
359372 if row ["mtime_ns" ] == timestamp or no_age_check :
360373 return dict ((column , row [column ]) for column in columns )
361374
362375 else :
363- # seconds = round((timestamp - e["timestamp"]) / 1000000000)
364- # delta = timedelta(seconds=seconds)
365- # print(f"{FEINT}{ITALIC}entry {key} is out of date for {delta}{RESET}", end=f"{CR}")
366-
367376 print_log (f"{ FEINT } { ITALIC } entry { key } is out of date{ RESET } " , end = TRANSIENT )
368377
369378 else :
@@ -704,11 +713,12 @@ def run_day(
704713 wait : float ,
705714 quiet : bool ,
706715):
707- elapsed = defaultdict (list )
716+ elapsed_by_lang = defaultdict (list )
708717
709718 day_suffix = mday .removeprefix (str (day ))
710719 name_max_len = 16 - len (day_suffix )
711720
721+ # for puzzle inputs
712722 for crc , file in sorted (day_inputs .items (), key = itemgetter (1 )):
713723 input_name = file .parent .parent .name
714724 input_name = input_name .removeprefix ("tmp-" )[:16 ]
@@ -722,44 +732,57 @@ def run_day(
722732 else :
723733 prefix = f"{ CYAN } { prefix } { RESET } "
724734
725- results = set ()
735+ # set to check if all answers are identical
736+ # answers_set = set()
726737
738+ # for all available languages
727739 for lang , (pattern , interpreter ) in languages .items ():
728740 prog = Path (pattern .format (year = year , day = mday , AOC_TARGET_DIR = Env .AOC_TARGET_DIR ))
729- key = SolutionKey (year , day , crc , str (prog ), lang .lower ())
730-
731- if not prog .is_file ():
732- # special case for day13_alt/day13.py
733- if "_" in prog .stem and prog .stem == prog .parent .name :
734- prog = prog .with_stem (prog .stem [: prog .stem .find ("_" )])
735-
736741 if not prog .is_file ():
737742 continue
738743
744+ key = SolutionKey (year , day , crc , str (prog ), lang .lower ())
745+
739746 if prune :
740747 prune_cache (key , "solutions" )
741748 continue
742749
743- if refresh :
744- e = None
745- in_cache = False
746- else :
747- e = check_cache (key , prog , "solutions" , ("elapsed" , "status" , "answers" ), dry_run )
748- in_cache = e is not None
750+ e = check_cache (key , prog , "solutions" , ("elapsed" , "status" , "answers" ), no_age_check = dry_run )
751+ in_cache = e is not None
749752
750- if not in_cache and not dry_run :
753+ timing_status = "☽" if in_cache else " "
754+
755+ if (not in_cache and not dry_run ) or refresh :
751756 nb_expected = 1 if day == 25 else 2
752757
758+ cached_e = e
759+
753760 e = run (prog , lang , interpreter , file , day_answers .get (crc ), nb_expected , year , day , quiet )
754761
755762 if e :
756- update_cache (key , prog , "solutions" , e )
763+ timing = e ["elapsed" ]
764+
765+ # if a valid solution exists in the database
766+ if cached_e and cached_e ["status" ] in ("ok" , "unknown" ):
767+ # if the solution is valid and faster than the cached one, update it
768+ if e ["status" ] in ("ok" , "unknown" ) and cached_e ["elapsed" ] > timing :
769+ update_cache (key , prog , "solutions" , e )
770+ timing_status = "☀️"
771+ else :
772+ # otyherwire, the timing for the solution is the cached one
773+ timing = cached_e ["elapsed" ]
774+ else :
775+ # no cached solution or invalid solution in the cache
776+ update_cache (key , prog , "solutions" , e )
757777
758778 if wait is not None :
759779 if not quiet :
760780 print_log (f"{ CR } { CLEAR_EOL } waiting { wait :g} s..." , end = "" )
761781 time .sleep (wait )
762782
783+ else :
784+ timing = e ["elapsed" ]
785+
763786 if not e :
764787 continue
765788
@@ -790,7 +813,7 @@ def run_day(
790813 f" { YELLOW } { lang :<7} { RESET } :"
791814 f" { status_color } { e ['status' ]:7} { RESET } "
792815 f" { WHITE } { e ['elapsed' ] / 1e9 :7.3f} s"
793- f" { GRAY } { '☽' if in_cache else ' ' } "
816+ f" { GRAY } { timing_status } "
794817 )
795818
796819 if quiet :
@@ -805,20 +828,23 @@ def run_day(
805828 if e ["status" ] in ("error" , "failed" ):
806829 problems .append (line )
807830
808- results .add (answers )
831+ # answers_set .add(answers)
809832
810- elapsed [lang ].append (e [ "elapsed" ] / 1e9 )
833+ elapsed_by_lang [lang ].append (timing / 1e9 )
811834
812- # if len(results ) > 1:
835+ # if len(answers_set ) > 1:
813836 # line = f"{prefix} {RED}{BLINK}MISMATCH BETWEEN SOLUTIONS{RESET}"
814837 # print(line)
815838 # problems.append(line)
816839
817- nb_samples = set (len (t ) for _ , t in elapsed .items ())
818- assert len (nb_samples ) == 1 or len (nb_samples ) == 0
819- nb_samples = 0 if len (nb_samples ) == 0 else nb_samples .pop ()
840+ samples_set = set (len (i ) for i in elapsed_by_lang .values ())
841+ if not dry_run :
842+ # if not dry run, all languages should have the same puzzle input count
843+ # if dry run, some languages may have not be run
844+ assert len (samples_set ) == 1 or len (samples_set ) == 0
845+ nb_samples = 0 if len (samples_set ) == 0 else max (samples_set )
820846
821- return dict ((lang , sum (t ) / len (t )) for lang , t in elapsed .items ()), nb_samples
847+ return dict ((lang , sum (t ) / len (t )) for lang , t in elapsed_by_lang .items ()), nb_samples
822848
823849
824850def get_languages (filter_lang : t .Iterable [str ]) -> t .Dict [str , t .Tuple [str , t .Union [str , None ]]]:
@@ -1146,9 +1172,6 @@ def main():
11461172 except KeyboardInterrupt :
11471173 pass
11481174
1149- # except Exception as e:
1150- # print(f"{RED}ERROR {e}{RESET}")
1151-
11521175 finally :
11531176 if stats_elapsed :
11541177 languages = sorted (set (map (itemgetter (3 ), stats_elapsed .keys ())))
0 commit comments