diff --git a/UGiveSeedFertilizer.pas b/UGiveSeedFertilizer.pas index cc4b76a..7bd6575 100644 --- a/UGiveSeedFertilizer.pas +++ b/UGiveSeedFertilizer.pas @@ -18,30 +18,41 @@ unit UGiveSeedFertilizer; {$mode ObjFPC}{$H+} +{$modeswitch AdvancedRecords+} interface uses - Classes, SysUtils, fgl, USolver; + Classes, SysUtils, fgl, Math, USolver; type + { TValueRange } + + TValueRange = record + Part: Integer; + Start, Length: Cardinal; + class operator = (Left, Right : TValueRange): Boolean; + end; + + TValueRanges = specialize TFPGList; + { TAlmanacMapRange } TAlmanacMapRange = class private - FDestinationStart, FSourceStart, FSourceEnd, FDifference1, FDifference2: Cardinal; + FDestinationStart, FSourceStart, FSourceLast, FLength, FDifference1, FDifference2: Cardinal; public constructor Create(const ALine: string); constructor Create(const ADestinationStart, ASourceStart, ALength: Cardinal); - function TryConvert(const AInput: Cardinal; out OOutput: Cardinal): Boolean; + procedure TryConvert(const AInput: TValueRange; const AUnconverted, AConverted: TValueRanges); end; { TGiveSeedFertilizer } TGiveSeedFertilizer = class(TSolver) private - FNew, FConverted, FProcessedIndices: specialize TFPGList; + FNew, FUnconverted, FConverted: TValueRanges; procedure ProcessSeeds(const ALine: string); procedure ProcessMapRange(const ALine: string); procedure FinishMap; @@ -56,6 +67,13 @@ type implementation +{ TValueRange } + +class operator TValueRange. = (Left, Right: TValueRange): Boolean; +begin + Result := (Left.Part = Right.Part) and (Left.Start = Right.Start) and (Left.Length = Right.Length); +end; + { TAlmanacMapRange } constructor TAlmanacMapRange.Create(const ALine: string); @@ -70,12 +88,13 @@ constructor TAlmanacMapRange.Create(const ADestinationStart, ASourceStart, ALeng begin FDestinationStart := ADestinationStart; FSourceStart := ASourceStart; + FLength := ALength; - // All input values are within Cardinal range, but the calculations might not be. - if ASourceStart <= Cardinal.MaxValue - ALength then - FSourceEnd := ASourceStart + ALength + // All input values are within Cardinal range, but the calculation results might not be. + if ASourceStart <= Cardinal.MaxValue - ALength + 1 then + FSourceLast := ASourceStart + ALength - 1 else - FSourceEnd := Cardinal.MaxValue; + FSourceLast := Cardinal.MaxValue; if ADestinationStart >= ASourceStart then begin @@ -88,19 +107,44 @@ begin end; end; -function TAlmanacMapRange.TryConvert(const AInput: Cardinal; out OOutput: Cardinal): Boolean; +procedure TAlmanacMapRange.TryConvert(const AInput: TValueRange; const AUnconverted, AConverted: TValueRanges); +var + inputLast: Cardinal; + splitValue: TValueRange; begin - if (FSourceStart <= AInput) and (AInput < FSourceEnd) then + if AInput.Start <= Cardinal.MaxValue - AInput.Length + 1 then + inputLast := AInput.Start + AInput.Length - 1 + else + inputLast := Cardinal.MaxValue; + + splitValue.Part := AInput.Part; + + // Converts the convertible part of the value range, if any. + if Min(FSourceLast, inputLast) >= Max(FSourceStart, AInput.Start) then begin + splitValue.Start := Max(FSourceStart, AInput.Start); + splitValue.Length := Min(FSourceLast, inputLast) - splitValue.Start + 1; if FDifference1 > 0 then - OOutput := AInput + FDifference1 + Inc(splitValue.Start, FDifference1) else - OOutput := AInput - FDifference2; - Result := True; - end - else begin - OOutput := 0; - Result := False; + Dec(splitValue.Start, FDifference2); + AConverted.Add(splitValue); + end; + + // Splits off the part before the convertible part, if any. + if FSourceStart > AInput.Start then + begin + splitValue.Start := AInput.Start; + splitValue.Length := Min(FSourceStart - AInput.Start, AInput.Length); + AUnconverted.Add(splitValue); + end; + + // Splits off the part after the convertible part, if any. + if inputLast > FSourceLast then + begin + splitValue.Start := Max(FSourceLast + 1, AInput.Start); + splitValue.Length := Min(inputLast - FSourceLast, AInput.Length); + AUnconverted.Add(splitValue); end; end; @@ -110,43 +154,51 @@ procedure TGiveSeedFertilizer.ProcessSeeds(const ALine: string); var split: TStringArray; i: Integer; + value: TValueRange; begin split := Aline.Split(' '); - for i := 1 to Length(split) - 1 do + for i := 1 to Length(split) div 2 do begin - FNew.Add(StrToDWord(split[i])); + // Adds values for part 1. + value.Part := 1; + value.Length := 1; + value.Start := StrToDWord(split[2 * i - 1]); + FNew.Add(value); + value.Start := StrToDWord(split[2 * i]); + FNew.Add(value); + + // Adds values for part 2. + value.Part := 2; + value.Start := StrToDWord(split[2 * i - 1]); + value.Length := StrToDWord(split[2 * i]); + FNew.Add(value); end; end; procedure TGiveSeedFertilizer.ProcessMapRange(const ALine: string); var range: TAlmanacMapRange; - i: Integer; - converted: Cardinal; + value: TValueRange; + temp: TValueRanges; begin - // Converts all "new" values that fall into this mapping range. + // Tries to convert all value ranges that have not yet been converted by the current map. range := TAlmanacMapRange.Create(ALine); - for i := 0 to FNew.Count - 1 do + for value in FNew do begin - if range.TryConvert(FNew[i], converted) then - begin - FConverted.Add(converted); - FProcessedIndices.Add(i); - end; + range.TryConvert(value, FUnconverted, FConverted); end; range.Free; - // Forgets the converted values. - for i := FProcessedIndices.Count - 1 downto 0 do - begin - FNew.Delete(FProcessedIndices[i]); - end; - FProcessedIndices.Clear; + // Discards all new and moves unconverted back as new. + FNew.Clear; + temp := FNew; + FNew := FUnconverted; + FUnconverted := temp; end; procedure TGiveSeedFertilizer.FinishMap; var - value: Cardinal; + value: TValueRange; begin // Resets the lists for the next map, remaining values stay unconverted. for value in FConverted do @@ -158,16 +210,16 @@ end; constructor TGiveSeedFertilizer.Create; begin - FNew := specialize TFPGList.Create; - FConverted := specialize TFPGList.Create; - FProcessedIndices := specialize TFPGList.Create; + FNew := TValueRanges.Create; + FUnconverted := TValueRanges.Create; + FConverted := TValueRanges.Create; end; destructor TGiveSeedFertilizer.Destroy; begin FNew.Free; + FUnconverted.Free; FConverted.Free; - FProcessedIndices.Free; inherited Destroy; end; @@ -183,13 +235,20 @@ end; procedure TGiveSeedFertilizer.Finish; var - value: Cardinal; + value: TValueRange; begin FinishMap; for value in FNew do begin - if (FPart1 = 0) or (value < FPart1) then - FPart1 := value; + if value.Part = 1 then + begin + if (FPart1 = 0) or (value.Start < FPart1) then + FPart1 := value.Start; + end + else begin + if (FPart2 = 0) or (value.Start < FPart2) then + FPart2 := value.Start; + end; end; end; diff --git a/tests/UGiveSeedFertilizerTestCases.pas b/tests/UGiveSeedFertilizerTestCases.pas index 4e42cce..8d04d6e 100644 --- a/tests/UGiveSeedFertilizerTestCases.pas +++ b/tests/UGiveSeedFertilizerTestCases.pas @@ -62,7 +62,7 @@ end; procedure TGiveSeedFertilizerFullDataTestCase.TestPart2; begin - AssertEquals(-1, FSolver.GetResultPart2); + AssertEquals(99751240, FSolver.GetResultPart2); end; { TGiveSeedFertilizerExampleTestCase } @@ -79,7 +79,7 @@ end; procedure TGiveSeedFertilizerExampleTestCase.TestPart2; begin - AssertEquals(-1, FSolver.GetResultPart2); + AssertEquals(46, FSolver.GetResultPart2); end; initialization