diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi
index d22fbc2..eba98fb 100644
--- a/AdventOfCode.lpi
+++ b/AdventOfCode.lpi
@@ -113,6 +113,10 @@
+
+
+
+
diff --git a/UIntervals.pas b/UIntervals.pas
new file mode 100644
index 0000000..48cd938
--- /dev/null
+++ b/UIntervals.pas
@@ -0,0 +1,232 @@
+{
+ Solutions to the Advent Of Code.
+ Copyright (C) 2023 Stefan Müller
+
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+}
+
+unit UIntervals;
+
+{$mode ObjFPC}{$H+}
+{$modeswitch AdvancedRecords+}
+
+interface
+
+uses
+ Classes, SysUtils, Generics.Collections, Math;
+
+type
+
+ { TInterval }
+
+ TInterval = record
+ Start, Length: Cardinal;
+ function GetLast: Cardinal;
+ end;
+
+ TIntervals = specialize TList;
+
+ { TIntervalSet }
+
+ TIntervalSet = class
+ private
+ FIntervals: TIntervals;
+ function GetCount: Cardinal;
+ procedure Intersect(const AInterval1, AInterval2: TInterval; out OIntersection: TInterval);
+ procedure Difference(const AInterval1, AInterval2: TInterval; out OBefore, OAfter: TInterval);
+ public
+ property Count: Cardinal read GetCount;
+ constructor Create;
+ constructor Create(constref AInterval: TInterval);
+ constructor Create(const AIntervals: TIntervals);
+ constructor Create(const AStart, ALength: Cardinal);
+ destructor Destroy; override;
+ function Contains(const AValue: Cardinal): Boolean;
+ function IsEmpty: Boolean;
+ function Intersect(const AOther: TIntervalSet): TIntervalSet;
+ function Difference(const AOther: TIntervalSet): TIntervalSet;
+ function Clone: TIntervalSet;
+ end;
+
+implementation
+
+{ TInterval }
+
+function TInterval.GetLast: Cardinal;
+begin
+ if Start <= Cardinal.MaxValue - Length + 1 then
+ Result := Start + Length - 1
+ else
+ Result := Cardinal.MaxValue;
+end;
+
+{ TIntervalSet }
+
+function TIntervalSet.GetCount: Cardinal;
+var
+ interval: TInterval;
+begin
+ Result := 0;
+ for interval in FIntervals do
+ Inc(Result, interval.Length);
+end;
+
+procedure TIntervalSet.Intersect(const AInterval1, AInterval2: TInterval; out OIntersection: TInterval);
+var
+ minLast: Cardinal;
+begin
+ minLast := Min(AInterval1.GetLast, AInterval2.GetLast);
+
+ if minLast >= Max(AInterval1.Start, AInterval2.Start) then
+ begin
+ OIntersection.Start := Max(AInterval1.Start, AInterval2.Start);
+ OIntersection.Length := minLast - OIntersection.Start + 1;
+ end
+ else begin
+ OIntersection.Start := 0;
+ OIntersection.Length := 0;
+ end;
+end;
+
+// Returns in OBefore the part of AInterval1 that lies before AInterval2, and in OAfter the part of AInterval1 that lies
+// after AInterval2. Either could be empty. OBefore + OAfter would be equivalent to AInterval1 - AInterval2.
+procedure TIntervalSet.Difference(const AInterval1, AInterval2: TInterval; out OBefore, OAfter: TInterval);
+var
+ last1, last2: Cardinal;
+begin
+ if AInterval2.Start > AInterval1.Start then
+ begin
+ OBefore.Start := AInterval1.Start;
+ OBefore.Length := Min(AInterval2.Start - AInterval1.Start, AInterval1.Length);
+ end
+ else begin
+ OBefore.Start := 0;
+ OBefore.Length := 0;
+ end;
+
+ last1 := AInterval1.GetLast;
+ last2 := AInterval2.GetLast;
+ if last1 > last2 then
+ begin
+ OAfter.Start := Max(last2 + 1, AInterval1.Start);
+ OAfter.Length := Min(last1 - last2, AInterval1.Length);
+ end
+ else begin
+ OAfter.Start := 0;
+ OAfter.Length := 0;
+ end;
+end;
+
+constructor TIntervalSet.Create;
+begin
+ FIntervals := TIntervals.Create;
+end;
+
+constructor TIntervalSet.Create(constref AInterval: TInterval);
+begin
+ Create;
+ if AInterval.Length > 0 then
+ FIntervals.Add(AInterval);
+end;
+
+constructor TIntervalSet.Create(const AIntervals: TIntervals);
+begin
+ Create;
+ FIntervals.AddRange(AIntervals);
+end;
+
+constructor TIntervalSet.Create(const AStart, ALength: Cardinal);
+var
+ interval: TInterval;
+begin
+ interval.Start := AStart;
+ interval.Length := ALength;
+ Create(interval);
+end;
+
+destructor TIntervalSet.Destroy;
+begin
+ FIntervals.Free;
+ inherited Destroy;
+end;
+
+function TIntervalSet.Contains(const AValue: Cardinal): Boolean;
+var
+ interval: TInterval;
+begin
+ Result := False;
+ for interval in FIntervals do
+ if (interval.Start <= AValue) and (AValue <= interval.GetLast) then
+ begin
+ Result := True;
+ Exit;
+ end;
+end;
+
+function TIntervalSet.IsEmpty: Boolean;
+begin
+ Result := FIntervals.Count = 0;
+end;
+
+function TIntervalSet.Intersect(const AOther: TIntervalSet): TIntervalSet;
+var
+ interval1, interval2, intersection: TInterval;
+begin
+ Result := TIntervalSet.Create;
+ for interval2 in AOther.FIntervals do
+ for interval1 in FIntervals do
+ begin
+ Intersect(interval1, interval2, intersection);
+ if intersection.Length > 0 then
+ Result.FIntervals.Add(intersection);
+ end;
+end;
+
+function TIntervalSet.Difference(const AOther: TIntervalSet): TIntervalSet;
+var
+ interval1, interval2, before, after: TInterval;
+ current, next, temp: TIntervals;
+begin
+ current := TIntervals.Create(FIntervals);
+ next := TIntervals.Create;
+ for interval2 in AOther.FIntervals do
+ begin
+ for interval1 in current do
+ begin
+ Difference(interval1, interval2, before, after);
+ if before.Length > 0 then
+ next.Add(before);
+ if after.Length > 0 then
+ next.Add(after);
+ end;
+ current.Clear;
+ temp := current;
+ current := next;
+ next := temp;
+ end;
+ Result := TIntervalSet.Create(current);
+ current.Free;
+ next.Free;
+end;
+
+function TIntervalSet.Clone: TIntervalSet;
+var
+ interval: TInterval;
+begin
+ Result := TIntervalSet.Create;
+ for interval in FIntervals do
+ Result.FIntervals.Add(interval);
+end;
+
+end.
+
diff --git a/solvers/UAplenty.pas b/solvers/UAplenty.pas
index 4d32c30..e6f6f56 100644
--- a/solvers/UAplenty.pas
+++ b/solvers/UAplenty.pas
@@ -22,7 +22,7 @@ unit UAplenty;
interface
uses
- Classes, SysUtils, Generics.Collections, USolver;
+ Classes, SysUtils, Generics.Collections, USolver, UIntervals;
type
TMachinePartCategory = (mpcExtremelyCoolLookingCategoryIndex, mpcMusicalCategoryIndex, mpcAerodynamicCategoryIndex,
@@ -40,6 +40,29 @@ type
function CalcRating: Integer;
end;
+ TWorkflowRuleEffect = class;
+
+ { TMultiMachinePart }
+
+ TMultiMachinePart = class
+ private
+ FCategories: array[TMachinePartCategory] of TIntervalSet;
+ FNextEffect: TWorkflowRuleEffect;
+ function GetCategory(const AIndex: TMachinePartCategory): TIntervalSet;
+ public
+ property Category[AIndex: TMachinePartCategory]: TIntervalSet read GetCategory;
+ property NextEffect: TWorkflowRuleEffect read FNextEffect write FNextEffect;
+ constructor CreateEmpty;
+ constructor Create;
+ destructor Destroy; override;
+ function IsEmpty: Boolean;
+ function CalcCombinations: Int64;
+ function Clone(const AReplaceCategory: TMachinePartCategory; const AReplaceCategoryValue: TIntervalSet):
+ TMultiMachinePart;
+ end;
+
+ TMultiMachineParts = specialize TObjectList;
+
TWorkflow = class;
TWorkflowRuleEffectType = (wetAccept, wetReject, wetMove);
@@ -64,14 +87,15 @@ type
TWorkflowRule = class
private
FCategory: TMachinePartCategory;
- FLessThan: Boolean;
- FThreshold: Integer;
+ FMatches: TIntervalSet;
FEffect: TWorkflowRuleEffect;
public
property Effect: TWorkflowRuleEffect read FEffect;
constructor Create(const ALine: string);
destructor Destroy; override;
function IsMatch(const AMachinePart: TMachinePart): Boolean;
+ function GetMatched(const AMultiMachinePart: TMultiMachinePart): TMultiMachinePart;
+ function GetUnmatched(const AMultiMachinePart: TMultiMachinePart): TMultiMachinePart;
end;
TWorkflowRules = specialize TObjectList;
@@ -88,6 +112,7 @@ type
constructor Create(const ALine: string);
destructor Destroy; override;
function Process(const AMachinePart: TMachinePart): TWorkflowRuleEffect;
+ function Process(const AMultiMachinePart: TMultiMachinePart): TMultiMachineParts;
end;
TWorkflows = specialize TObjectList;
@@ -101,6 +126,8 @@ type
FStart: TWorkflow;
procedure ProcessWorkflowLine(const ALine: string);
procedure ProcessMachinePartLine(const ALine: string);
+ function CalcAcceptedMachinePartCount: Int64;
+ procedure CheckMoveDestination(const AEffect: TWorkflowRuleEffect);
public
constructor Create;
destructor Destroy; override;
@@ -116,6 +143,8 @@ const
CRejected = 'R';
CLessThanChar = '<';
CStartWorkflowName = 'in';
+ CCategoryMinimum = 1;
+ CCategoryMaximum = 4000;
implementation
@@ -146,6 +175,70 @@ begin
Inc(Result, cat);
end;
+{ TMultiMachinePart }
+
+function TMultiMachinePart.GetCategory(const AIndex: TMachinePartCategory): TIntervalSet;
+begin
+ Result := FCategories[AIndex];
+end;
+
+constructor TMultiMachinePart.CreateEmpty;
+begin
+
+end;
+
+constructor TMultiMachinePart.Create;
+var
+ cat: TMachinePartCategory;
+begin
+ for cat in TMachinePartCategory do
+ FCategories[cat] := TIntervalSet.Create(CCategoryMinimum, CCategoryMaximum - CCategoryMinimum + 1);
+end;
+
+destructor TMultiMachinePart.Destroy;
+var
+ cat: TMachinePartCategory;
+begin
+ for cat in TMachinePartCategory do
+ FCategories[cat].Free;
+ inherited Destroy;
+end;
+
+function TMultiMachinePart.IsEmpty: Boolean;
+var
+ cat: TIntervalSet;
+begin
+ Result := False;
+ for cat in FCategories do
+ if cat.IsEmpty then
+ begin
+ Result := True;
+ Exit;
+ end;
+end;
+
+function TMultiMachinePart.CalcCombinations: Int64;
+var
+ cat: TIntervalSet;
+begin
+ Result := 1;
+ for cat in FCategories do
+ Result := Result * cat.Count;
+end;
+
+function TMultiMachinePart.Clone(const AReplaceCategory: TMachinePartCategory; const AReplaceCategoryValue:
+ TIntervalSet): TMultiMachinePart;
+var
+ cat: TMachinePartCategory;
+begin
+ Result := TMultiMachinePart.CreateEmpty;
+ for cat in TMachinePartCategory do
+ if cat = AReplaceCategory then
+ Result.FCategories[cat] := AReplaceCategoryValue
+ else
+ Result.FCategories[cat] := FCategories[cat].Clone;
+end;
+
{ TWorkflowRuleEffect }
constructor TWorkflowRuleEffect.Create(const ALine: string);
@@ -171,6 +264,7 @@ end;
constructor TWorkflowRule.Create(const ALine: string);
var
split: TStringArray;
+ threshold: Cardinal;
begin
case ALine[1] of
'x': FCategory := mpcExtremelyCoolLookingCategoryIndex;
@@ -178,25 +272,44 @@ begin
'a': FCategory := mpcAerodynamicCategoryIndex;
's': FCategory := mpcShinyCategoryIndex;
end;
- FLessThan := Aline[2] = CLessThanChar;
split := ALine.Split(':');
- FThreshold := StrToInt(Copy(split[0], 3, Length(split[0]) - 2));
+ threshold := StrToInt(Copy(split[0], 3, Length(split[0]) - 2));
+ if Aline[2] = CLessThanChar then
+ FMatches := TIntervalSet.Create(CCategoryMinimum, threshold - 1)
+ else
+ FMatches := TIntervalSet.Create(threshold + 1, CCategoryMaximum - threshold);
+
FEffect := TWorkflowRuleEffect.Create(split[1]);
end;
destructor TWorkflowRule.Destroy;
begin
FEffect.Free;
+ FMatches.Free;
inherited Destroy;
end;
function TWorkflowRule.IsMatch(const AMachinePart: TMachinePart): Boolean;
begin
- if FLessThan then
- Result := AMachinePart.Category[FCategory] < FThreshold
- else
- Result := AMachinePart.Category[FCategory] > FThreshold;
+ Result := FMatches.Contains(AMachinePart.Category[FCategory]);
+end;
+
+function TWorkflowRule.GetMatched(const AMultiMachinePart: TMultiMachinePart): TMultiMachinePart;
+var
+ new: TIntervalSet;
+begin
+ new := AMultiMachinePart.Category[FCategory].Intersect(FMatches);
+ Result := AMultiMachinePart.Clone(FCategory, new);
+ Result.NextEffect := FEffect;
+end;
+
+function TWorkflowRule.GetUnmatched(const AMultiMachinePart: TMultiMachinePart): TMultiMachinePart;
+var
+ new: TIntervalSet;
+begin
+ new := AMultiMachinePart.Category[FCategory].Difference(FMatches);
+ Result := AMultiMachinePart.Clone(FCategory, new);
end;
{ TWorkflow }
@@ -240,6 +353,42 @@ begin
Result := FLastEffect;
end;
+function TWorkflow.Process(const AMultiMachinePart: TMultiMachinePart): TMultiMachineParts;
+var
+ rule: TWorkflowRule;
+ current, new: TMultiMachinePart;
+begin
+ Result := TMultiMachineParts.Create(False);
+ current := AMultiMachinePart;
+ for rule in FRules do
+ begin
+ if rule.Effect.EffectType <> wetReject then
+ begin
+ new := rule.GetMatched(current);
+ if new.IsEmpty then
+ new.Free
+ else
+ Result.Add(new);
+ end;
+ new := rule.GetUnmatched(current);
+ current.Free;
+ if new.IsEmpty then
+ begin
+ new.Free;
+ Exit;
+ end
+ else
+ current := new;
+ end;
+ if FLastEffect.EffectType <> wetReject then
+ begin
+ current.NextEffect := FLastEffect;
+ Result.Add(current);
+ end
+ else
+ current.Free;
+end;
+
{ TAplenty }
procedure TAplenty.ProcessWorkflowLine(const ALine: string);
@@ -255,7 +404,7 @@ end;
procedure TAplenty.ProcessMachinePartLine(const ALine: string);
var
part: TMachinePart;
- workflow, search: TWorkflow;
+ workflow: TWorkflow;
effect: TWorkflowRuleEffect;
begin
part := TMachinePart.Create(ALine);
@@ -264,13 +413,7 @@ begin
effect := workflow.Process(part);
if effect.EffectType = wetMove then
begin
- if effect.MoveDestination = nil then
- for search in FWorkflows do
- if search.Name = effect.MoveDestinationName then
- begin
- effect.MoveDestination := search;
- Break;
- end;
+ CheckMoveDestination(effect);
workflow := effect.MoveDestination;
end;
until effect.EffectType <> wetMove;
@@ -280,6 +423,53 @@ begin
part.Free;
end;
+function TAplenty.CalcAcceptedMachinePartCount: Int64;
+var
+ part: TMultiMachinePart;
+ parts: TMultiMachineParts;
+ stack: specialize TStack;
+ workflow: TWorkflow;
+begin
+ Result := 0;
+ stack := specialize TStack.Create;
+ stack.Push(TMultiMachinePart.Create);
+
+ repeat
+ part := stack.Pop;
+ if part.NextEffect = nil then
+ workflow := FStart
+ else begin
+ CheckMoveDestination(part.NextEffect);
+ workflow := part.NextEffect.MoveDestination;
+ end;
+ parts := workflow.Process(part);
+ for part in parts do
+ if part.NextEffect.EffectType = wetAccept then
+ begin
+ Inc(Result, part.CalcCombinations);
+ part.Free;
+ end
+ else
+ stack.Push(part);
+ parts.Free;
+ until stack.Count = 0;
+
+ stack.Free;
+end;
+
+procedure TAplenty.CheckMoveDestination(const AEffect: TWorkflowRuleEffect);
+var
+ destination: TWorkflow;
+begin
+ if AEffect.MoveDestination = nil then
+ for destination in FWorkflows do
+ if destination.Name = AEffect.MoveDestinationName then
+ begin
+ AEffect.MoveDestination := destination;
+ Break;
+ end;
+end;
+
constructor TAplenty.Create;
begin
FWorkflows := TWorkflows.Create;
@@ -309,7 +499,7 @@ end;
procedure TAplenty.Finish;
begin
-
+ FPart2 := CalcAcceptedMachinePartCount;
end;
function TAplenty.GetDataFileName: string;
diff --git a/solvers/UGiveSeedFertilizer.pas b/solvers/UGiveSeedFertilizer.pas
index 7bd6575..3b3b84f 100644
--- a/solvers/UGiveSeedFertilizer.pas
+++ b/solvers/UGiveSeedFertilizer.pas
@@ -29,6 +29,7 @@ type
{ TValueRange }
+ // TODO: Use TIntervalSet instead.
TValueRange = record
Part: Integer;
Start, Length: Cardinal;
diff --git a/tests/UAplentyTestCases.pas b/tests/UAplentyTestCases.pas
index 6ed66a8..907f129 100644
--- a/tests/UAplentyTestCases.pas
+++ b/tests/UAplentyTestCases.pas
@@ -62,7 +62,7 @@ end;
procedure TAplentyFullDataTestCase.TestPart2;
begin
- AssertEquals(-1, FSolver.GetResultPart2);
+ AssertEquals(121464316215623, FSolver.GetResultPart2);
end;
{ TAplentyExampleTestCase }
@@ -79,7 +79,7 @@ end;
procedure TAplentyExampleTestCase.TestPart2;
begin
- AssertEquals(-1, FSolver.GetResultPart2);
+ AssertEquals(167409079868000, FSolver.GetResultPart2);
end;
initialization