Quick Algorithm: Display an Array of Integers as Set of Integer Ranges

Say you have an array of integer values, for example, pages of a document or years when something happened, like years when new Delphi version was released:

delphiReleased: TArray<integer> = 
 [1995, 1996, 1997, 1998, 1999, 2001, 2002, 2003, 
  2005, 2006, 2007, 2009, 2010, 2011, 2012, 2013, 
  2014, 2015, 2016, 2017, 2018, 2020, 2021, 2022];

If you would want to display the above years in a user friendly manner as a set of ranges, you could go for:

1995 - 1999, 2001 - 2003, 2005 - 2007, 2009 - 2018, 2020 - 2022

Here’s an algorithm (in a console app code) that does the above

program range_display;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Generics.Defaults, System.Generics.Collections;

type
  TRange = record
    Start, Stop : integer;
    function ToString : string;
    constructor Create(const _start, _stop : integer);
  end;

function RangeDisplay(intArray: TArray<integer>) : string; overload;
var
  snRange : TRange;
  snRangeList : TList<TRange>;
  i : integer;
begin
  if Length(intArray) = 0 then Exit ('');

  TArray.Sort<integer>(intArray);

  snRangeList := TList<TRange>.Create;
  try
    snRange := TRange.Create(intArray[Low(intArray)], intArray[Low(intArray)]);

    for i := 1 + Low(intArray) to High(intArray) do
    begin
      if (intArray[i] = Succ(snRange.Stop)) OR (intArray[i] = snRange.Stop) then
        snRange.Stop := intArray[i]
      else
      begin
        snRangeList.Add(snRange);

        snRange := TRange.Create(intArray[i], intArray[i]);
      end;
    end;

    snRangeList.Add(snRange);

    result := snRangeList.First.ToString;

    snRangeList.Delete(0);

    for snRange in snRangeList do
      result := result + ', ' + snRange.ToString;

  finally
    snRangeList.Free;
  end;
end;

function RangeDisplay(const intList: TList<integer>) : string; overload;
begin
  result := RangeDisplay(intList.ToArray);
end;


{$region 'TRange'}
constructor TRange.Create(const _start, _stop: integer);
begin
  Start := _start;
  Stop := _stop;
end;

function TRange.ToString: string;
begin
  if Start = Stop then
    result := Start.ToString()
  else
    result := Format('%s - %s',[Start.ToString, Stop.ToString]);
end;
{$endregion 'TRange'}

const
  emptyArray : TArray<integer> = [];
  intArray1 : TArray<integer> = [1, 2, 4, 6, 7, 10, 11, 9, 9, 12, 300, 301]; //9 is a duplicate
  intArray2 : TArray<integer> = [3, 4, 6, 7, 8, 9, 10, 12, 13, 15, 16];
begin
  try
    Writeln('emptyArray:' + RangeDisplay(emptyArray));
    Writeln('intArray1:' + RangeDisplay(intArray1));
    Writeln('intArray2:' + RangeDisplay(intArray2));

    ReadLn;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

The result when running the above:

emptyArray:
intArray1:1 - 2, 4, 6 - 7, 9 - 12, 300 - 301
intArray2:3 - 4, 6 - 10, 12 - 13, 15 - 16

That’s it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.