diff --git a/solvers/UGearRatios.pas b/solvers/UGearRatios.pas index d93548f..647960d 100644 --- a/solvers/UGearRatios.pas +++ b/solvers/UGearRatios.pas @@ -22,21 +22,59 @@ unit UGearRatios; interface uses - Classes, SysUtils, USolver; + Classes, SysUtils, fgl, USolver; const CDigitChars = ['0'..'9']; CNonSymbolChars = ['0'..'9', '.']; + CGearChar = '*'; type + { TGearCandidate } + + TGearCandidate = class + private + FColumn, FPartNumber1, FPartNumber2, FPartNumber3: Integer; + public + constructor Create(AColumn: Integer); + procedure AddPartNumber(APartNumber: Integer); + function GetGearRatio: Integer; + property Column: Integer read FColumn; + end; + + { TGearCandidates } + + TGearCandidates = specialize TFPGObjectList; + + { TGearCandidateTracker } + + TGearCandidateTracker = class + private + FPreviousLine, FLine, FNextLine, FCurrentNumber: TGearCandidates; + public + constructor Create; + destructor Destroy; override; + function GetLine(AIndex: Integer): TGearCandidates; + function FinishLine: Integer; + property PreviousLine: TGearCandidates read FPreviousLine; + property Line: TGearCandidates read FLine; + property NextLine: TGearCandidates read FNextLine; + property CurrentNumber: TGearCandidates read FCurrentNumber; + end; + { TGearRatios } TGearRatios = class(TSolver) private FPreviousLine, FLine: string; + FGearTracker: TGearCandidateTracker; procedure ProcessDataLineTriplet(const APreviousLine, ALine, ANextLine: string); + function HasSymbolCharInColumn(const AIndex: Integer; const ALines: array of string): Boolean; + procedure LogForCurrentGearCandidates(const APartNumber: Integer); public + constructor Create; + destructor Destroy; override; procedure ProcessDataLine(const ALine: string); override; procedure Finish; override; function GetDataFileName: string; override; @@ -45,6 +83,80 @@ type implementation +{ TGearCandidate } + +constructor TGearCandidate.Create(AColumn: Integer); +begin + FColumn := AColumn; + FPartNumber1 := 0; + FPartNumber2 := 0; + FPartNumber3 := 0; +end; + +procedure TGearCandidate.AddPartNumber(APartNumber: Integer); +begin + if FPartNumber1 = 0 then + FPartNumber1 := APartNumber + else if FPartNumber2 = 0 then + FPartNumber2 := APartNumber + else + FPartNumber3 := APartNumber; +end; + +function TGearCandidate.GetGearRatio: Integer; +begin + if FPartNumber3 = 0 then + Result := FPartNumber1 * FPartNumber2 + else + Result := 0; +end; + +{ TGearCandidateTracker } + +constructor TGearCandidateTracker.Create; +begin + FPreviousLine := TGearCandidates.Create; + FLine := TGearCandidates.Create; + FNextLine := TGearCandidates.Create; + FCurrentNumber := TGearCandidates.Create(False); +end; + +destructor TGearCandidateTracker.Destroy; +begin + FPreviousLine.Free; + FLine.Free; + FNextLine.Free; + FCurrentNumber.Free; + inherited Destroy; +end; + +function TGearCandidateTracker.GetLine(AIndex: Integer): TGearCandidates; +begin + case AIndex of + 0: Result := FPreviousLine; + 1: Result := FLine; + 2: Result := FNextLine; + else Result := nil; + end; +end; + +function TGearCandidateTracker.FinishLine: Integer; +var + candidate: TGearCandidate; + temp: TGearCandidates; +begin + Result := 0; + for candidate in FPreviousLine do + begin + Inc(Result, candidate.GetGearRatio); + end; + FPreviousLine.Clear; + temp := FPreviousLine; + FPreviousLine := FLine; + FLine := FNextLine; + FNextLine := temp; +end; + { TGearRatios } procedure TGearRatios.ProcessDataLineTriplet(const APreviousLine, ALine, ANextLine: string); @@ -61,35 +173,21 @@ begin begin inNumber := True; numberStart := i; + FGearTracker.CurrentNumber.Clear; // Checks for a symbol in the column before the first digit. - isPartNumber := (i > 1) and - (not (APreviousLine[i - 1] in CNonSymbolChars) - or not (ALine[i - 1] in CNonSymbolChars) - or not (ANextLine[i - 1] in CNonSymbolChars)); + isPartNumber := (i > 1) and HasSymbolCharInColumn(i - 1, [APreviousLine, ALine, ANextLine]); end; // Checks for a symbol in the column of the current digit. - if inNumber and not isPartNumber and - (not (APreviousLine[i] in CNonSymbolChars) - or not (ANextLine[i] in CNonSymbolChars)) then - begin + if inNumber and HasSymbolCharInColumn(i, [APreviousLine, ALine, ANextLine]) then isPartNumber := True; - end; // Checks if number ends. if inNumber and (not (ALine[i] in CDigitChars) or (i = ALine.Length)) then begin inNumber := False; - // Checks for a symbol in the column after the last digit. - if not (APreviousLine[i] in CNonSymbolChars) - or not (ALine[i] in CNonSymbolChars) - or not (ANextLine[i] in CNonSymbolChars) then - begin - isPartNumber := True; - end; - // Counts if it is a part number. if isPartNumber then begin @@ -98,9 +196,71 @@ begin Inc(numberLength); partNumber := StrToInt(Copy(ALine, numberStart, numberLength)); Inc(FPart1, partNumber); + LogForCurrentGearCandidates(partNumber); end; end; end; + + Inc(FPart2, FGearTracker.FinishLine); +end; + +function TGearRatios.HasSymbolCharInColumn(const AIndex: Integer; const ALines: array of string): Boolean; +var + i: Integer; + candidates: TGearCandidates; + candidate: TGearCandidate; + exists: Boolean; +begin + Result := False; + for i := 0 to High(ALines) do + if not (ALines[i][AIndex] in CNonSymbolChars) then + begin + Result := True; + // Could exit here for part 1, the following is only for part 2. + if ALines[i][AIndex] = CGearChar then + begin + candidates := FGearTracker.GetLine(i); + exists := False; + for candidate in candidates do + begin + if candidate.Column = AIndex then + begin + // The current gear candidate has already been found. + FGearTracker.CurrentNumber.Add(candidate); + exists := True; + Break; + end; + end; + if not exists then + begin + // A new gear candidate was found. + candidate := TGearCandidate.Create(AIndex); + candidates.Add(candidate); + FGearTracker.CurrentNumber.Add(candidate); + end; + end; + end; +end; + +procedure TGearRatios.LogForCurrentGearCandidates(const APartNumber: Integer); +var + candidate: TGearCandidate; +begin + for candidate in FGearTracker.CurrentNumber do + begin + candidate.AddPartNumber(APartNumber); + end; +end; + +constructor TGearRatios.Create; +begin + FGearTracker := TGearCandidateTracker.Create; +end; + +destructor TGearRatios.Destroy; +begin + FGearTracker.Free; + inherited Destroy; end; procedure TGearRatios.ProcessDataLine(const ALine: string); diff --git a/tests/UGearRatiosTestCases.pas b/tests/UGearRatiosTestCases.pas index 14817db..6568d86 100644 --- a/tests/UGearRatiosTestCases.pas +++ b/tests/UGearRatiosTestCases.pas @@ -44,6 +44,7 @@ type procedure TearDown; override; published procedure TestPart1; + procedure TestPart2; end; { TGearRatiosTestCase } @@ -97,6 +98,12 @@ begin AssertEquals(530495, FSolver.GetResultPart1); end; +procedure TGearRatiosFullDataTestCase.TestPart2; +begin + AssertEquals(80253814, FSolver.GetResultPart2); +end; + + { TGearRatiosTestCase } procedure TGearRatiosTestCase.TestEndOfLineNumber; diff --git a/tests/bin/AdventOfCodeFPCUnit.fpcunit.ini b/tests/bin/AdventOfCodeFPCUnit.fpcunit.ini index cc80332..c7501c6 100644 --- a/tests/bin/AdventOfCodeFPCUnit.fpcunit.ini +++ b/tests/bin/AdventOfCodeFPCUnit.fpcunit.ini @@ -11,6 +11,8 @@ TGearRatiosFullDataTestCase.Checked=1 TGearRatiosFullDataTestCase.Expanded=1 TGearRatiosFullDataTestCase.TestPart1.Checked=1 TGearRatiosFullDataTestCase.TestPart1.Expanded=0 +TGearRatiosFullDataTestCase.TestPart2.Checked=1 +TGearRatiosFullDataTestCase.TestPart2.Expanded=0 TGearRatiosTestCase.Checked=1 TGearRatiosTestCase.Expanded=1 TGearRatiosTestCase.TestEndOfLineNumber.Checked=1