11unit WeatherStation;
22
33{ $mode objfpc}{ $H+}{ $J-}{ $modeSwitch advancedRecords}
4-
54interface
65
76uses
87 { $IFDEF UNIX}
9- cthreads,
8+ cmem, cthreads,
109 { $ENDIF}
1110 Classes,
1211 SysUtils,
13- Generics.Collections
12+ Generics.Collections,
13+ Math
1414 { $IFDEF DEBUG}
1515, Stopwatch
1616 { $ENDIF}
17- ;
17+ ;
1818
1919type
2020 // Create a record of temperature stats
2121 TStat = record
2222 var
23- min: single;
24- max: single;
25- sum: single;
26- count: word;
23+ min: int64; // Borrowed the concept from go's approach to improve performance, save floats as int64
24+ max: int64; // This saved ~2 mins processing time.
25+ sum: int64;
26+ cnt: int64;
27+ private
28+ function RoundEx (x: double): double; // Borrowed from the baseline program
29+ function PascalRound (x: double): double; // Borrowed from the baseline program
2730 public
28- constructor Create(newMin: single;
29- newMax: single;
30- newSum: single;
31- newCount: word);
31+ constructor Create(const newMin: int64; const newMax: int64;
32+ const newSum: int64; const newCount: int64);
3233 function ToString : string;
34+
3335 end ;
3436
3537type
3638 // Create a dictionary
3739 TWeatherDictionary = specialize TDictionary<string, TStat>;
3840
41+ // The main algorithm to process the temp measurements from various weather station
42+ procedure ProcessTempMeasurements (const filename: string);
3943
40- { // A helper function to add a city temperature into the TWeatherDictionary
41- procedure AddCityTemperature(cityName: string;
42- newTemp: single;
43- var weatherDictionary: TWeatherDictionary);
44- }
44+ implementation
4545
46- // The main algorithm to process the temp measurements from various weather station
47- procedure ProcessTempMeasurements (filename: string);
46+ function TStat.RoundEx (x: double): double;
47+ begin
48+ Result := PascalRound(x * 10.0 ) / 10.0 ;
49+ end ;
4850
51+ function TStat.PascalRound (x: double): double;
52+ var
53+ t: double;
54+ begin
55+ // round towards positive infinity
56+ t := Trunc(x);
57+ if (x < 0.0 ) and (t - x = 0.5 ) then
58+ begin
59+ // Do nothing
60+ end
61+ else if Abs(x - t) >= 0.5 then
62+ begin
63+ t := t + Math.Sign(x);
64+ end ;
4965
50- implementation
66+ if t = 0.0 then
67+ Result := 0.0
68+ else
69+ Result := t;
70+ end ;
5171
52- constructor TStat.Create(newMin: single;
53- newMax: single;
54- newSum: single;
55- newCount: word);
72+ constructor TStat.Create(const newMin: int64; const newMax: int64;
73+ const newSum: int64; const newCount: int64);
5674begin
5775 self.min := newMin;
5876 self.max := newMax;
5977 self.sum := newSum;
60- self.count := newCount;
78+ self.cnt := newCount;
6179end ;
6280
6381function TStat.ToString : string;
82+ var
83+ minR, meanR, maxR: double; // Store the rounded values prior saving to TStringList.
6484begin
65- { $IFDEF DEBUG}
66- Result := Format(' Min: %.1f; Mean: %.1f; Maxp: %.1f; Sum: %.1f; Count %d' ,
67- [self.min, (self.sum / self.Count), self.max,
68- self.sum, self.Count]);
69- { $ENDIF DEBUG}
70- // Result := Format('%.1f/%.1f/%.1f', [self.min, (self.sum / self.count), self.max]);
71- Result := FormatFloat(' 0.0' , self.min) + ' /' + FormatFloat(' 0.0' , (self.sum/self.count)) + ' /' + FormatFloat(' 0.0' , self.max)
72-
85+ minR := RoundEx(self.min / 10 );
86+ maxR := RoundEx(self.max / 10 );
87+ meanR := RoundEx(self.sum / self.cnt / 10 );
88+ Result := FormatFloat(' 0.0' , minR) + ' /' + FormatFloat(' 0.0' , meanR) +
89+ ' /' + FormatFloat(' 0.0' , maxR);
7390end ;
7491
7592{
@@ -78,10 +95,11 @@ function TStat.ToString: string;
7895 The following procedure Written by Székely Balázs for the 1BRC for Object Pascal.
7996 URL: https://github.com/gcarreno/1brc-ObjectPascal/tree/main
8097}
81- function CustomTStringListComparer (AList: TStringList; AIndex1, AIndex2: Integer): Integer;
98+ function CustomTStringListComparer (AList: TStringList;
99+ AIndex1, AIndex2: integer): integer;
82100var
83- Pos1, Pos2: Integer ;
84- Str1, Str2: String ;
101+ Pos1, Pos2: integer ;
102+ Str1, Str2: string ;
85103begin
86104 Result := 0 ;
87105 Str1 := AList.Strings[AIndex1];
@@ -92,24 +110,19 @@ function CustomTStringListComparer(AList: TStringList; AIndex1, AIndex2: Integer
92110 begin
93111 Str1 := Copy(Str1, 1 , Pos1 - 1 );
94112 Str2 := Copy(Str2, 1 , Pos2 - 1 );
95- Result := CompareStr(Str1, Str2);
113+ Result := CompareStr(Str1, Str2);
96114 end ;
97115end ;
98116
99- procedure AddCityTemperature (cityName: string;
100- newTemp: single ;
101- var weatherDictionary: TWeatherDictionary);
117+
118+ procedure AddCityTemperature ( const cityName: string; const newTemp: int64 ;
119+ var weatherDictionary: TWeatherDictionary);
102120var
103121 stat: TStat;
104122begin
105- // If city name exists , modify temp as needed
123+ // If city name esxists , modify temp as needed
106124 if weatherDictionary.ContainsKey(cityName) then
107125 begin
108-
109- { $IFDEF DEBUG}
110- WriteLn(' City found: ' , cityName);
111- { $ENDIF DEBUG}
112-
113126 // Get the temp record
114127 stat := weatherDictionary[cityName];
115128
@@ -123,7 +136,7 @@ procedure AddCityTemperature(cityName: string;
123136 stat.sum := stat.sum + newTemp;
124137
125138 // Increase the counter
126- stat.Count := stat.Count + 1 ;
139+ stat.cnt := stat.cnt + 1 ;
127140
128141 // Update the stat of this city
129142 weatherDictionary.AddOrSetValue(cityName, stat);
@@ -133,27 +146,20 @@ procedure AddCityTemperature(cityName: string;
133146 if not weatherDictionary.ContainsKey(cityName) then
134147 begin
135148 weatherDictionary.Add(cityName, TStat.Create(newTemp, newTemp, newTemp, 1 ));
136- { $IFDEF DEBUG}
137- WriteLn(' Added: ' , cityName);
138- { $ENDIF DEBUG}
139149 end ;
140150end ;
141151
142- procedure ProcessTempMeasurements (filename: string);
152+ procedure ProcessTempMeasurements (const filename: string);
143153var
144154 wd: TWeatherDictionary;
145- line, ws: string;
146- lineSeparated: array of string;
155+ line, ws, strTemp: string;
147156 weatherStationList: TStringList;
148157 textFile: System.TextFile;
149- isFirstKey: boolean = True;
158+ delimiterPos, valCode: integer;
159+ intTemp: int64;
160+ index: integer;
150161begin
151162
152- // Start a timer
153- { $IFDEF DEBUG}
154- Stopwatch.StartTimer;
155- { $ENDIF}
156-
157163 // Create a city - weather dictionary
158164 wd := TWeatherDictionary.Create;
159165 weatherStationList := TStringList.Create;
@@ -167,18 +173,36 @@ procedure ProcessTempMeasurements(filename: string);
167173 // Open the file for reading
168174 Reset(textFile);
169175
176+ { $IFDEF DEBUG}
177+ // Start a timer
178+ Stopwatch.StartTimer;
179+ { $ENDIF}
180+
170181 // Keep reading lines until the end of the file is reached
171182 while not EOF(textFile) do
172183 begin
173184 // Read a line
174185 ReadLn(textFile, line);
175- // If the line start with #, then continue/skip.
176- if (line[1 ] = ' #' ) then continue;
177-
178- // Else, add an entry into the dictionary.
179- lineSeparated := line.Split([' ;' ]);
180- AddCityTemperature(lineSeparated[0 ], StrToFloat(lineSeparated[1 ]), wd);
181186
187+ // Get position of the delimiter
188+ delimiterPos := Pos(' ;' , line);
189+ if delimiterPos > 0 then
190+ begin
191+ // Get the weather station name
192+ // Using Copy and POS - as suggested by Gemini AI.
193+ // This part saves 3 mins faster when processing 1 billion rows.
194+ ws := Copy(line, 1 , delimiterPos - 1 );
195+
196+ // Get the temperature recorded, as string, remove '.' from string float
197+ // because we want to save it as int64.
198+ strTemp := Copy(line, delimiterPos + 1 , Length(line));
199+ strTemp := StringReplace(strTemp, ' .' , ' ' , [rfReplaceAll]);
200+
201+ // Add the weather station and the recorded temp (as int64) in the TDictionary
202+ Val(strTemp, intTemp, valCode);
203+ if valCode <> 0 then Continue;
204+ AddCityTemperature(ws, intTemp, wd);
205+ end ;
182206 end ; // end while loop reading line at a time
183207
184208 // Close the file
@@ -189,45 +213,52 @@ procedure ProcessTempMeasurements(filename: string);
189213 WriteLn(' File handling error occurred. Details: ' , E.Message);
190214 end ; // End of file reading ////////////////////////////////////////////////
191215
216+ { $IFDEF DEBUG}
217+ Stopwatch.StopTimer;
218+ WriteLn(' Finished reading and updating dictionary' );
219+ Stopwatch.DisplayTimer;
220+ { $ENDIF}
221+
192222 // Format and sort weather station by name and temp stat ///////////////////
223+ { $IFDEF DEBUG}
224+ Stopwatch.StartTimer;
225+ { $ENDIF}
226+ ws := ' ' ;
193227 for ws in wd.Keys do
194228 begin
195- weatherStationList.Add(ws + ' =' + wd[ws].ToString);
229+ weatherStationList.Add(ws + ' =' + wd[ws].ToString + ' , ' );
196230 end ;
197231 weatherStationList.CustomSort(@CustomTStringListComparer);
198232
199- // Print TStringList - sorted by weather station and temp stat /////////////
200- Write(' {' );
201- for ws in weatherStationList do
202- begin
203- // If it's not the first key, print a comma
204- if not isFirstKey then
205- Write(' , ' );
206-
207- // Print the weather station and the temp stat
208- Write(ws);
209-
210- // Set isFirstKey to False after printing the first key
211- isFirstKey := False;
212- end ;
233+ { $IFDEF DEBUG}
234+ Stopwatch.StopTimer;
235+ WriteLn(' Finished creating TStringList and sorted it' );
236+ Stopwatch.DisplayTimer;
237+ { $ENDIF}
213238
214- WriteLn(' }' );
239+ // Print TStringList - sorted by weather station and temp stat /////////////
240+ { $IFDEF DEBUG}
241+ Stopwatch.StartTimer;
242+ { $ENDIF}
243+ strTemp := ' ' ;
244+ // Print the weather station and the temp stat
245+ for index := 0 to weatherStationList.Count - 1 do
246+ strTemp := strTemp + weatherStationList[index];
247+ // Remove last comma and space; ', ', a neat trick from Gus.
248+ SetLength(strTemp, Length(strTemp) - 2 );
249+ WriteLn(' {' , strTemp, ' }' );
215250
216251 { $IFDEF DEBUG}
217- WriteLn(' DEBUG mode on' );
218- { $ENDIF DEBUG}
252+ Stopwatch.StopTimer;
253+ WriteLn(' Finished printing the sorted weather station and temperatures' );
254+ Stopwatch.DisplayTimer;
255+ { $ENDIF}
219256
220257 finally
221258 weatherStationList.Free;
222259 wd.Free;
223260 end ; // End of processing TDictionary and TStringList
224261
225- // Stop a timer
226- { $IFDEF DEBUG}
227- Stopwatch.StopTimer;
228- Stopwatch.DisplayTimer;
229- { $ENDIF}
230-
231262end ;
232263
233264end .
0 commit comments