{ 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.