program RangeCombo;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Math, System.StrUtils, Generics.Defaults, Generics.Collections;
type
Func<TResult> = reference to function(): TResult;
Func<T, TResult> = reference to function(const Arg1: T): TResult;
Func<T1, T2, TResult> = reference to function(const Arg1: T1; const Arg2: T2): TResult;
Func<T1, T2, T3, TResult> = reference to function(const Arg1: T1; const Arg2: T2; const Arg3: T3): TResult;
// various algorithms
Alg = record
class function Aggregate<T, TAccumulate>(const Input: TArray<T>; const F: Func<TAccumulate, T, TAccumulate>): TAccumulate; overload; static;
class function Aggregate<T, TAccumulate>(const Input: TArray<T>; const InitialValue: TAccumulate; const F: Func<TAccumulate, T, TAccumulate>): TAccumulate; overload; static;
class function Distinct<T>(const Input: TArray<T>): TArray<T>; static;
class function Sort<T>(const Input: TArray<T>): TArray<T>; overload; static;
class function Transform<T>(const Input: TArray<T>; const F: Func<T, T>): TArray<T>; overload; static;
class function Transform<T, TResult>(const Input: TArray<T>; const F: Func<T, TResult>): TArray<TResult>; overload; static;
end;
{ Alg }
class function Alg.Aggregate<T, TAccumulate>(const Input: TArray<T>;
const F: Func<TAccumulate, T, TAccumulate>): TAccumulate;
begin
result := Aggregate<T, TAccumulate>(Input, Default(TAccumulate), F);
end;
class function Alg.Aggregate<T, TAccumulate>(const Input: TArray<T>;
const InitialValue: TAccumulate;
const F: Func<TAccumulate, T, TAccumulate>): TAccumulate;
var
elm: T;
begin
result := InitialValue;
for elm in Input do
begin
result := F(result, elm);
end;
end;
class function Alg.Distinct<T>(const Input: TArray<T>): TArray<T>;
var
dict: TDictionary<T, integer>;
isPresent: boolean;
i, j: NativeInt;
begin
dict := nil;
try
dict := TDictionary<T, integer>.Create;
SetLength(result, Length(Input));
j := 0;
for i := 0 to High(Input) do
begin
isPresent := dict.ContainsKey(Input[i]);
if (isPresent) then
continue;
dict.Add(Input[i], 1);
result[j] := Input[i];
j := j + 1;
end;
SetLength(result, j);
finally
dict.Free;
end;
end;
class function Alg.Sort<T>(const Input: TArray<T>): TArray<T>;
begin
result := Copy(Input);
TArray.Sort<T>(result, TComparer<T>.Default);
end;
class function Alg.Transform<T, TResult>(const Input: TArray<T>;
const F: Func<T, TResult>): TArray<TResult>;
var
i: NativeInt;
begin
SetLength(result, Length(Input));
for i := 0 to High(Input) do
result[i] := F(Input[i]);
end;
class function Alg.Transform<T>(const Input: TArray<T>; const F: Func<T, T>): TArray<T>;
var
i: NativeInt;
begin
SetLength(result, Length(Input));
for i := 0 to High(Input) do
result[i] := F(Input[i]);
end;
type
Functional = record
public
type BindArg1 = record end;
type BindArg2 = record end;
public
class function Bind<T1, T2, T3, TResult>(const f: Func<T1, T2, T3, TResult>; const Arg1: BindArg1; const Arg2: T2; const Arg3: T3): Func<T1, TResult>; overload; static;
end;
const _1: Functional.BindArg1 = ();
const _2: Functional.BindArg2 = ();
{ Functional }
class function Functional.Bind<T1, T2, T3, TResult>(const f: Func<T1, T2, T3, TResult>; const Arg1: BindArg1;
const Arg2: T2; const Arg3: T3): Func<T1, TResult>;
begin
result :=
function(const Arg1: T1): TResult
begin
result := f(Arg1, Arg2, Arg3);
end;
end;
type
Range = record
First: integer;
Last: integer;
class operator Implicit(const Value: integer): Range;
class function FromString(const Str: string): Range; static;
class function ToString(const R: Range): string; overload; static;
function ToString(): string; overload;
end;
function IsSingularRange(const R: Range): boolean;
begin
result := (R.First = R.Last);
end;
{ Range }
class function Range.FromString(const Str: string): Range;
var
elms: TArray<string>;
begin
elms := Str.Split(['-']);
case Length(elms) of
1: begin
result := elms[0].ToInteger();
end;
2: begin
result.First := elms[0].ToInteger();
result.Last := elms[1].ToInteger();
end;
else
raise EArgumentException.CreateFmt('Invalid range: "%s"', [Str]);
end;
end;
class operator Range.Implicit(const Value: integer): Range;
begin
result.First := Value;
result.Last := Value;
end;
class function Range.ToString(const R: Range): string;
begin
result := R.ToString();
end;
function Range.ToString: string;
begin
result := First.ToString();
if (not IsSingularRange(Self)) then
result := result + '-' + Last.ToString();
end;
function CombineRanges(const Input: TArray<integer>): TArray<Range>;
var
sortedInput: TArray<integer>;
begin
sortedInput := Alg.Sort<integer>(Input);
result := Alg.Aggregate<integer, TArray<Range>>(sortedInput,
function(const Accumulator: TArray<Range>; const Value: integer): TArray<Range>
var
r: Range;
begin
if (Length(Accumulator) = 0) then
begin
result := [Value];
exit;
end;
r := Accumulator[High(Accumulator)];
if (Value > (r.Last + 1)) then
begin
// need a new range
result := Accumulator + [Value];
end
else
begin
// extend last range
result := Accumulator;
result[High(result)].Last := System.Math.Max(r.Last, Value);
end
end
);
end;
function ExpandRanges(const Input: TArray<Range>): TArray<integer>;
begin
result := Alg.Sort<integer>(
Alg.Distinct<integer>(
Alg.Aggregate<Range, TArray<integer>>(Input,
function(const Accumulator: TArray<integer>; const Value: Range): TArray<integer>
var
i: integer;
begin
result := Accumulator;
for i := Value.First to Value.Last do
begin
result := result + [i];
end;
end
)
)
);
end;
var
input: TArray<integer>;
ranges: TArray<Range>;
rangesStr: string;
values: TArray<integer>;
valuesStr: string;
begin
input := [1,2,3,4,6,7,8,13,14];
try
ranges := CombineRanges(input);
rangesStr := string.Join(
', ',
Alg.Transform<Range, string>(
ranges,
Range.ToString
)
);
WriteLn(rangesStr);
WriteLn;
WriteLn;
rangesStr := '3, 13-16, 15 - 17, 1';
ranges := Alg.Transform<string, Range>(
Alg.Transform<string, string>(
rangesStr.Split([',']), // split all range elements
Functional.Bind<string, string, string, string>(ReplaceStr, _1, ' ', '') // remove any spaces
),
Range.FromString
);
values := ExpandRanges(ranges);
valuesStr := string.Join(
', ',
Alg.Transform<integer, string>(
values,
integer.ToString
)
);
WriteLn(valuesStr);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
WriteLn;
WriteLn('done');
ReadLn;
end.