with Ada.Characters.Latin_1, Ada.Containers.Vectors, Ada.Text_IO, CSV; package body Candidates.Containers is procedure Read_Candidates (Filename : in String; State : in State_Name; Candidate_Data : out Candidate_Vector) is package My_CSV is new CSV; use Ada.Text_IO; use type SU.Unbounded_String; Input_File : File_Type; Current_Record : My_CSV.CSV_Record; Current_Candidate : Candidate; begin Open (Input_File, In_File, Filename); Candidate_Data := Candidate_Vectors.Empty_Vector; while not End_Of_File (Input_File) loop Current_Record := My_CSV.Parse_Line (Get_Line (Input_File)); -- All the field numbers here correspond to how -- AEC Senate candidate data is arranged in csv format. if Integer (Current_Record.Length) = 25 and then Current_Record.Element (2) = "S" and then Current_Record.Element (3) = State_Name'Image (State) then Current_Candidate := (First_Name => Current_Record.Element (8), Last_Name => Current_Record.Element (7), Group => Current_Record.Element (5), Group_Rank => Current_Record.Element (6), Party => Current_Record.Element (9)); Candidate_Data.Append (Current_Candidate); end if; end loop; Close (Input_File); end Read_Candidates; -- These types exist because I can't think of an easier -- way to sort a Candidate_Map into the appropriate order at -- the moment. type Cand_Sort_Data is record Cand_ID : CandidateID; Group : SU.Unbounded_String; Group_Rank : SU.Unbounded_String; end record; package Cand_Sort_Data_Vectors is new Ada.Containers.Vectors (Index_Type => Positive, Element_Type => Cand_Sort_Data); function "<" (Left, Right : Cand_Sort_Data) return Boolean is use type SU.Unbounded_String; begin if SU.Length (Left.Group) = SU.Length (Right.Group) then if Left.Group = Right.Group then if SU.Length (Left.Group_Rank) = SU.Length (Right.Group_Rank) then return Left.Group_Rank < Right.Group_Rank; else return SU.Length (Left.Group_Rank) < SU.Length (Right.Group_Rank); end if; else return Left.Group < Right.Group; end if; else return SU.Length (Left.Group) < SU.Length (Right.Group); end if; end "<"; function Generate_Above (Candidate_Data : in Cand_Sort_Data_Vectors.Vector) return Above_Line_Ballot is use type Ada.Containers.Count_Type; use type SU.Unbounded_String; Result : Above_Line_Ballot := CandidateID_Map_Maps.Empty_Map; Working_Map : CandidateID_Maps.Map; Current_Group : SU.Unbounded_String; Current_Index : Positive; Next_ID, Working_ID : Positive; begin if Candidate_Data.Length = 0 then return Result; end if; Next_ID := 1; Current_Index := Candidate_Data.First_Index; while Current_Index <= Candidate_Data.Last_Index loop Current_Group := Candidate_Data.Element (Current_Index).Group; -- The assumption is that the "UG" group is always last. -- A fairly safe assumption given alphabetical group order -- but will break down should there be more than 553 grouped candidates. -- Fortunately, that will never happen because the CandidateIDs will -- run out at 256 Candidates. exit when Current_Group = "UG"; Working_Map := CandidateID_Maps.Empty_Map; Working_ID := 1; loop Working_Map.Insert (Working_ID, Candidate_Data.Element (Current_Index).Cand_ID); Working_ID := Working_ID + 1; Current_Index := Current_Index + 1; exit when Current_Index > Candidate_Data.Last_Index or else Current_Group /= Candidate_Data.Element (Current_Index).Group; end loop; Result.Insert (Next_ID, Working_Map); Next_ID := Next_ID + 1; end loop; return Result; end Generate_Above; function Generate_Below (Candidate_Data : in Cand_Sort_Data_Vectors.Vector) return Below_Line_Ballot is Result : Below_Line_Ballot := CandidateID_Maps.Empty_Map; Next_ID : Positive := 1; begin for Item of Candidate_Data loop Result.Insert (Next_ID, Item.Cand_ID); Next_ID := Next_ID + 1; end loop; return Result; end Generate_Below; procedure Generate_Ballots (Candidate_Data : in Candidate_Vector; Above_Ballot : out Above_Line_Ballot; Below_Ballot : out Below_Line_Ballot) is package Sorting is new Cand_Sort_Data_Vectors.Generic_Sorting; My_Candidate_Data : Cand_Sort_Data_Vectors.Vector; Working_Candidate : Candidate; begin My_Candidate_Data := Cand_Sort_Data_Vectors.Empty_Vector; for Cursor in Candidate_Data.Iterate loop Working_Candidate := Candidate_Vectors.Element (Cursor); My_Candidate_Data.Append ((Cand_ID => Candidate_Vectors.To_Index (Cursor), Group => Working_Candidate.Group, Group_Rank => Working_Candidate.Group_Rank)); end loop; Sorting.Sort (My_Candidate_Data); Above_Ballot := Generate_Above (My_Candidate_Data); Below_Ballot := Generate_Below (My_Candidate_Data); end Generate_Ballots; function To_String (Above_Ballot : in Above_Line_Ballot) return String is Result : SU.Unbounded_String := SU.To_Unbounded_String (0); begin for Group_Cursor in Above_Ballot.Iterate loop SU.Append (Result, Integer'Image (CandidateID_Map_Maps.Key (Group_Cursor)) & ": "); for Box of CandidateID_Map_Maps.Element (Group_Cursor) loop SU.Append (Result, CandidateID'Image (Box) & " "); end loop; SU.Append (Result, Ada.Characters.Latin_1.LF); end loop; return SU.To_String (Result); end To_String; function To_String (Below_Ballot : in Below_Line_Ballot) return String is Result : SU.Unbounded_String := SU.To_Unbounded_String (0); begin for Box of Below_Ballot loop SU.Append (Result, CandidateID'Image (Box) & " "); end loop; return SU.To_String (Result); end To_String; end Candidates.Containers;