-- One Billion Row Challenge - Implement a silly and trivial programming challenge. -- Copyright (C) 2024 Prince Trippy <programmer@verisimilitudes.net>. -- This program is free software: you can redistribute it and/or modify it under the terms of the -- GNU Affero General Public License version 3 as published by the Free Software Foundation. -- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -- even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -- See the GNU Affero General Public License for more details. -- You should have received a copy of the GNU Affero General Public License along with this program. -- If not, see <http://www.gnu.org/licenses/>. -- This program requires Ada 2012 purely for want of a predefined sorting procedure and naught else. with Ada.Text_IO, Ada.Strings.Fixed, Ada.Command_Line, Ada.Containers.Generic_Array_Sort; with Trivial_Trie; procedure Billion_Row_Challenge is Billion : constant := 1_000_000_000; subtype Count is Natural range 0 .. Billion; type Index is new Natural range 0 .. 10_000; -- This is the guaranteed maximum count of stations. -- These were fixed point types, but the challenge demands those peculiarities of floating point. -- It would be interesting to test and look if decimal fixed point would get the correct results. type Temperature is digits 3 range -99.9 .. 99.9; type Average is digits 12 range -99.9 * Billion .. 99.9 * Billion; -- I wanted to use the F'Image attribute for this, but it doesn't work so well as in fixed point. package Temperature_IO is new Ada.Text_IO.Float_IO(Temperature); package Average_IO is new Ada.Text_IO.Float_IO(Average); type Indirect_String is access String; type Stations is array (Index range <>) of Indirect_String; -- A sort needs unconstrained arrays. type Intermediate is record Minimum, Maximum : Temperature; Sum : Average; Amount : Count; end record; function Sort_Indirect_Strings (Left, Right : Indirect_String) return Boolean is begin return Left.all < Right.all; end Sort_Indirect_Strings; package Trie is new Trivial_Trie(Index_Type => Positive, Domain_Type => Character, Input_Type => String, Stored_Type => Intermediate); procedure Sort is new Ada.Containers.Generic_Array_Sort (Index_Type => Index, Element_Type => Indirect_String, Array_Type => Stations, "<" => Sort_Indirect_Strings); Tray : Trie.Trie; Them : Stations(1 .. Index'Last); Size : Index := 0; File : Ada.Text_IO.File_Type; begin Ada.Command_Line.Set_Exit_Status(Ada.Command_Line.Failure); if Ada.Command_Line.Argument_Count /= 1 then Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error, "Supply only one input file."); return; end if; Ada.Text_IO.Open(File, Mode => Ada.Text_IO.In_File, Name => Ada.Command_Line.Argument(1)); Ada.Text_IO.Set_Input(File); while not Ada.Text_IO.End_Of_File loop declare Line : String(1 .. 108); -- This is that guaranteed, maximum input line length incremented. Here : Boolean; Ends : Natural; Stop : Positive; Heat : Temperature; That : Intermediate; begin Ada.Text_IO.Get_Line(Line, Ends); Stop := Ada.Strings.Fixed.Index(Source => Line(1 .. Ends), Pattern => ";", Going => Ada.Strings.Backward); Heat := Temperature'Value(Line(Stop + 1 .. Ends)); Trie.Find(Path => Line(1 .. Stop - 1), Tree => Tray, Seen => Here, Unto => That); if not Here then Size := Size + 1; Them(Size) := new String'(Line(1 .. Stop - 1)); That := (Minimum | Maximum => Heat, Sum => 0.0, Amount => 0); end if; That.Minimum := Temperature'Min(That.Minimum, Heat); That.Maximum := Temperature'Max(That.Maximum, Heat); That.Amount := That.Amount + 1; That.Sum := That.Sum + Average(Heat); Trie.Give(Path => Line(1 .. Stop - 1), Tree => Tray, What => That); end; end loop; Sort(Them(1 .. Size)); Ada.Text_IO.Put('{'); for I in Index range 1 .. Size loop declare Station : String := Them(I).all; Here : Boolean; That : Intermediate; begin -- A superfluous space will be output rather than a minus sign for the positive numbers. Trie.Find(Path => Station, Tree => Tray, Seen => Here, Unto => That); Ada.Text_IO.Put(Station); Ada.Text_IO.Put('='); Temperature_IO.Put(Item => That.Minimum, Fore => 2, Aft => 1, Exp => 0); Ada.Text_IO.Put('/'); Average_IO.Put(Item => That.Sum / Average(That.Amount), Fore => 2, Aft => 1, Exp => 0); Ada.Text_IO.Put('/'); Temperature_IO.Put(Item => That.Maximum, Fore => 2, Aft => 1, Exp => 0); if I /= Size then Ada.Text_IO.Put(", "); end if; end; end loop; Ada.Text_IO.Put('}'); Ada.Text_IO.Flush(Ada.Text_IO.Standard_Output); Ada.Text_IO.Close(File); Ada.Command_Line.Set_Exit_Status(Ada.Command_Line.Success); exception when Storage_Error => Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error, "Working storage was exhausted."); when Constraint_Error => Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error, "A range check failed or an input line was malformed."); when others => Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error, "A file error occurred."); end Billion_Row_Challenge;