16
16
import argparse
17
17
from dataclasses import dataclass
18
18
from math import inf , isfinite
19
- from typing import Any , Callable , Final , Iterable , cast
19
+ from typing import Any , Callable , Final , Generator , Iterable , cast
20
20
21
21
from pycommons .io .console import logger
22
22
from pycommons .io .csv import (
@@ -552,21 +552,23 @@ def to_csv(results: Iterable[EndResult], file: str) -> Path:
552
552
logger (f"Writing end results to CSV file { path !r} ." )
553
553
path .ensure_parent_dir_exists ()
554
554
with path .open_for_write () as wt :
555
- csv_write (
556
- data = sorted (results ), consumer = line_writer (wt ),
557
- setup = CsvWriter ().setup ,
558
- get_column_titles = CsvWriter .get_column_titles ,
559
- get_row = CsvWriter .get_row ,
560
- get_header_comments = CsvWriter .get_header_comments ,
561
- get_footer_comments = CsvWriter .get_footer_comments ,
562
- get_footer_bottom_comments = CsvWriter .get_footer_bottom_comments )
555
+ consumer : Final [Callable [[str ], None ]] = line_writer (wt )
556
+ for r in csv_write (
557
+ data = sorted (results ),
558
+ setup = CsvWriter ().setup ,
559
+ column_titles = CsvWriter .get_column_titles ,
560
+ get_row = CsvWriter .get_row ,
561
+ header_comments = CsvWriter .get_header_comments ,
562
+ footer_comments = CsvWriter .get_footer_comments ,
563
+ footer_bottom_comments = CsvWriter .get_footer_bottom_comments ):
564
+ consumer (r )
563
565
logger (f"Done writing end results to CSV file { path !r} ." )
564
566
return path
565
567
566
568
567
- def from_csv (file : str , consumer : Callable [[ EndResult ], Any ],
569
+ def from_csv (file : str ,
568
570
filterer : Callable [[EndResult ], bool ]
569
- = lambda x : True ) -> None :
571
+ = lambda x : True ) -> Generator [ EndResult , None , None ] :
570
572
"""
571
573
Parse a given CSV file to get :class:`EndResult` Records.
572
574
@@ -575,22 +577,14 @@ def from_csv(file: str, consumer: Callable[[EndResult], Any],
575
577
:class:`list`
576
578
:param filterer: an optional filter function
577
579
"""
578
- if not callable (consumer ):
579
- raise type_error (consumer , "consumer" , call = True )
580
580
path : Final [Path ] = file_path (file )
581
581
logger (f"Now reading CSV file { path !r} ." )
582
-
583
- def __cons (r : EndResult , __c = consumer , __f = filterer ) -> None :
584
- """Consume a record."""
585
- if __f (r ):
586
- __c (r )
587
-
588
582
with path .open_for_read () as rd :
589
- csv_read (rows = rd ,
590
- setup = CsvReader ,
591
- parse_row = CsvReader .parse_row ,
592
- consumer = __cons )
593
-
583
+ for r in csv_read (rows = rd ,
584
+ setup = CsvReader ,
585
+ parse_row = CsvReader .parse_row ):
586
+ if filterer ( r ):
587
+ yield r
594
588
logger (f"Done reading CSV file { path !r} ." )
595
589
596
590
@@ -663,116 +657,115 @@ def setup(self, data: Iterable[EndResult]) -> "CsvWriter":
663
657
return self
664
658
return self
665
659
666
- def get_column_titles (self , dest : Callable [[ str ], None ] ) -> None :
660
+ def get_column_titles (self ) -> Iterable [ str ] :
667
661
"""
668
662
Get the column titles.
669
663
670
- :param dest : the destination string consumer
664
+ :returns : the column titles
671
665
"""
672
666
p : Final [str ] = self .scope
673
- dest (csv_scope (p , KEY_ALGORITHM ))
674
- dest (csv_scope (p , KEY_INSTANCE ))
675
- dest (csv_scope (p , KEY_OBJECTIVE_FUNCTION ))
667
+ data : list [str ] = [
668
+ KEY_ALGORITHM , KEY_INSTANCE , KEY_OBJECTIVE_FUNCTION ]
676
669
if self .__needs_encoding :
677
- dest ( csv_scope ( p , KEY_ENCODING ) )
678
- dest ( csv_scope ( p , KEY_RAND_SEED ) )
679
- dest ( csv_scope ( p , KEY_BEST_F ) )
680
- dest ( csv_scope ( p , KEY_LAST_IMPROVEMENT_FE ) )
681
- dest ( csv_scope ( p , KEY_LAST_IMPROVEMENT_TIME_MILLIS ) )
682
- dest ( csv_scope ( p , KEY_TOTAL_FES ) )
683
- dest ( csv_scope ( p , KEY_TOTAL_TIME_MILLIS ) )
670
+ data . append ( KEY_ENCODING )
671
+ data . append ( KEY_RAND_SEED )
672
+ data . append ( KEY_BEST_F )
673
+ data . append ( KEY_LAST_IMPROVEMENT_FE )
674
+ data . append ( KEY_LAST_IMPROVEMENT_TIME_MILLIS )
675
+ data . append ( KEY_TOTAL_FES )
676
+ data . append ( KEY_TOTAL_TIME_MILLIS )
684
677
685
678
if self .__needs_goal_f :
686
- dest ( csv_scope ( p , KEY_GOAL_F ) )
679
+ data . append ( KEY_GOAL_F )
687
680
if self .__needs_max_fes :
688
- dest ( csv_scope ( p , KEY_MAX_FES ) )
681
+ data . append ( KEY_MAX_FES )
689
682
if self .__needs_max_ms :
690
- dest (csv_scope (p , KEY_MAX_TIME_MILLIS ))
683
+ data .append (KEY_MAX_TIME_MILLIS )
684
+ return (csv_scope (p , q ) for q in data )
691
685
692
- def get_row (self , data : EndResult ,
693
- dest : Callable [[str ], None ]) -> None :
686
+ def get_row (self , data : EndResult ) -> Iterable [str ]:
694
687
"""
695
688
Render a single end result record to a CSV row.
696
689
697
690
:param data: the end result record
698
- :param dest : the string consumer
691
+ :returns : the row iterator
699
692
"""
700
- dest ( data .algorithm )
701
- dest ( data .instance )
702
- dest ( data .objective )
693
+ yield data .algorithm
694
+ yield data .instance
695
+ yield data .objective
703
696
if self .__needs_encoding :
704
- dest ( data .encoding if data .encoding else "" )
705
- dest ( hex (data .rand_seed ) )
706
- dest ( num_to_str (data .best_f ) )
707
- dest ( str (data .last_improvement_fe ) )
708
- dest ( str (data .last_improvement_time_millis ) )
709
- dest ( str (data .total_fes ) )
710
- dest ( str (data .total_time_millis ) )
697
+ yield data .encoding if data .encoding else ""
698
+ yield hex (data .rand_seed )
699
+ yield num_to_str (data .best_f )
700
+ yield str (data .last_improvement_fe )
701
+ yield str (data .last_improvement_time_millis )
702
+ yield str (data .total_fes )
703
+ yield str (data .total_time_millis )
711
704
if self .__needs_goal_f :
712
- dest ( num_or_none_to_str (data .goal_f ) )
705
+ yield num_or_none_to_str (data .goal_f )
713
706
if self .__needs_max_fes :
714
- dest ( int_or_none_to_str (data .max_fes ) )
707
+ yield int_or_none_to_str (data .max_fes )
715
708
if self .__needs_max_ms :
716
- dest ( int_or_none_to_str (data .max_time_millis ) )
709
+ yield int_or_none_to_str (data .max_time_millis )
717
710
718
- def get_header_comments (self , dest : Callable [[ str ], None ] ) -> None :
711
+ def get_header_comments (self ) -> Iterable [ str ] :
719
712
"""
720
713
Get any possible header comments.
721
714
722
- :param dest : the destination
715
+ :returns : the header comments
723
716
"""
724
- dest ("Experiment End Results" )
725
- dest ( "See the description at the bottom of the file." )
717
+ return ("Experiment End Results" ,
718
+ "See the description at the bottom of the file." )
726
719
727
- def get_footer_comments (self , dest : Callable [[ str ], None ] ) -> None :
720
+ def get_footer_comments (self ) -> Iterable [ str ] :
728
721
"""
729
722
Get any possible footer comments.
730
723
731
- :param dest : the destination
724
+ :returns : the footer comments
732
725
"""
733
- dest ( "" )
726
+ yield ""
734
727
scope : Final [str | None ] = self .scope
735
- dest ("Records describing the end results of single runs ("
736
- "single executions) of algorithms applied to optimization "
737
- "problems." )
738
- dest ("Each run is characterized by an algorithm setup, a problem "
739
- "instance, and a random seed." )
728
+ yield ("Records describing the end results of single runs ("
729
+ "single executions) of algorithms applied to optimization "
730
+ "problems." )
731
+ yield ("Each run is characterized by an algorithm setup, a problem "
732
+ "instance, and a random seed." )
740
733
if scope :
741
- dest ("All end result records start with prefix "
742
- f"{ scope } { SCOPE_SEPARATOR } ." )
743
- dest ( f"{ csv_scope (scope , KEY_ALGORITHM )} : { DESC_ALGORITHM } " )
744
- dest ( f"{ csv_scope (scope , KEY_INSTANCE )} : { DESC_INSTANCE } " )
745
- dest (f"{ csv_scope (scope , KEY_OBJECTIVE_FUNCTION )} :"
746
- f" { DESC_OBJECTIVE_FUNCTION } " )
734
+ yield ("All end result records start with prefix "
735
+ f"{ scope } { SCOPE_SEPARATOR } ." )
736
+ yield f"{ csv_scope (scope , KEY_ALGORITHM )} : { DESC_ALGORITHM } "
737
+ yield f"{ csv_scope (scope , KEY_INSTANCE )} : { DESC_INSTANCE } "
738
+ yield (f"{ csv_scope (scope , KEY_OBJECTIVE_FUNCTION )} :"
739
+ f" { DESC_OBJECTIVE_FUNCTION } " )
747
740
if self .__needs_encoding :
748
- dest (f"{ csv_scope (scope , KEY_ENCODING )} : { DESC_ENCODING } " )
749
- dest ( f"{ csv_scope (scope , KEY_RAND_SEED )} : { DESC_RAND_SEED } " )
750
- dest ( f"{ csv_scope (scope , KEY_BEST_F )} : { DESC_BEST_F } " )
751
- dest (f"{ csv_scope (scope , KEY_LAST_IMPROVEMENT_FE )} : "
752
- f"{ DESC_LAST_IMPROVEMENT_FE } " )
753
- dest (f"{ csv_scope (scope , KEY_LAST_IMPROVEMENT_TIME_MILLIS )} : "
754
- f"{ DESC_LAST_IMPROVEMENT_TIME_MILLIS } " )
755
- dest ( f"{ csv_scope (scope , KEY_TOTAL_FES )} : { DESC_TOTAL_FES } " )
756
- dest (f"{ csv_scope (scope , KEY_TOTAL_TIME_MILLIS )} : "
757
- f"{ DESC_TOTAL_TIME_MILLIS } " )
741
+ yield (f"{ csv_scope (scope , KEY_ENCODING )} : { DESC_ENCODING } " )
742
+ yield f"{ csv_scope (scope , KEY_RAND_SEED )} : { DESC_RAND_SEED } "
743
+ yield f"{ csv_scope (scope , KEY_BEST_F )} : { DESC_BEST_F } "
744
+ yield (f"{ csv_scope (scope , KEY_LAST_IMPROVEMENT_FE )} : "
745
+ f"{ DESC_LAST_IMPROVEMENT_FE } " )
746
+ yield (f"{ csv_scope (scope , KEY_LAST_IMPROVEMENT_TIME_MILLIS )} : "
747
+ f"{ DESC_LAST_IMPROVEMENT_TIME_MILLIS } " )
748
+ yield f"{ csv_scope (scope , KEY_TOTAL_FES )} : { DESC_TOTAL_FES } "
749
+ yield (f"{ csv_scope (scope , KEY_TOTAL_TIME_MILLIS )} : "
750
+ f"{ DESC_TOTAL_TIME_MILLIS } " )
758
751
if self .__needs_goal_f :
759
- dest ( f"{ csv_scope (scope , KEY_GOAL_F )} : { DESC_GOAL_F } " )
752
+ yield f"{ csv_scope (scope , KEY_GOAL_F )} : { DESC_GOAL_F } "
760
753
if self .__needs_max_fes :
761
- dest ( f"{ csv_scope (scope , KEY_MAX_FES )} : { DESC_MAX_FES } " )
754
+ yield f"{ csv_scope (scope , KEY_MAX_FES )} : { DESC_MAX_FES } "
762
755
if self .__needs_max_ms :
763
- dest (f"{ csv_scope (scope , KEY_MAX_TIME_MILLIS )} : "
764
- f"{ DESC_MAX_TIME_MILLIS } " )
756
+ yield (f"{ csv_scope (scope , KEY_MAX_TIME_MILLIS )} : "
757
+ f"{ DESC_MAX_TIME_MILLIS } " )
765
758
766
- def get_footer_bottom_comments (self , dest : Callable [[ str ], None ] ) -> None :
759
+ def get_footer_bottom_comments (self ) -> Iterable [ str ] :
767
760
"""
768
761
Get the footer bottom comments.
769
762
770
- :param dest : the destination to write to.
763
+ :returns : the footer comments
771
764
"""
772
- motipy_footer_bottom_comments (
773
- self , dest , ("The end results data is produced using module "
774
- "moptipy.evaluation.end_results." ))
775
- pycommons_footer_bottom_comments (self , dest )
765
+ yield from motipy_footer_bottom_comments (
766
+ self , ("The end results data is produced using module "
767
+ "moptipy.evaluation.end_results." ))
768
+ yield from pycommons_footer_bottom_comments (self )
776
769
777
770
778
771
class CsvReader :
0 commit comments