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