Using LaTeX to compact a list of numbers

Here is the bulk of what you need with the input in black and the output in red:

enter image description here

Notes:

  • The main thing missing here is to sort the initial list. There are numerous solutions on this site, but one I have been using is Sorting Comma Separated Lists defined with, or without macro. Another one is: How to sort an alphanumeric list
  • This also needs furhter testing for cases that were not tested.

Code:

\documentclass{article}
\usepackage{pgffor}
\usepackage{xstring}
\usepackage{etoolbox}
\usepackage{xcolor}

\newtoggle{StartedRange}
\newcommand{\LastNumber}{}%
\newcommand{\LastRangeStart}{}%
\newcommand*{\compactthis}[1]{%
    \edef\ExpandedParam{#1}% <-- Apply sorting here
    \ExpandedParam:
    \begingroup\color{red}%
    \togglefalse{StartedRange}%
    \foreach \x in \ExpandedParam {%
        \iftoggle{StartedRange}{%
            \pgfmathtruncatemacro\ExpectedNextNumber{\LastNumber+1}%
            \IfEq{\ExpectedNextNumber}{\x}{%
                %% Continue this range
            }{%
                \IfEq{\LastRangeStart}{\LastNumber}{%
                    %% Was a single member
                }{%
                    --\LastNumber% Close last range
                }%
                ,\, \x%        and start a new range
                \xdef\LastRangeStart{\x}%
            }%
       }{%
            \x% initial range
            \xdef\LastRangeStart{\x}%
            \global\toggletrue{StartedRange}%
        }%
        \xdef\LastNumber{\x}%
    }%
    %  Process any ranges at end of list:
    \IfEq{\LastRangeStart}{\LastNumber}{%
        %% Was a single member
    }{%
        --\LastNumber% Close last range
    }%
    \endgroup%
}%

\begin{document}
\compactthis{1,2,3,4,5,7,8,9, 11}

\compactthis{1,2, 12,13,18, 20}% Single member

\compactthis{1,2, 12,13,18, 19, 20}% range at end

\end{document}

EDITED to sort the input stream using a bubble sort (the macro \bubblesort can be used independently of the \compactthis macro, if desired).

A \listterminator must be set to ANY numerical value known not to be in the list (e.g., suitably large, negative, zero, etc.), currently set at 9999. As shown (both in code and demonstrated in the MWE), the macro \adjtie can be set to -- if 1--2 is preferred to 1, 2 for adjacent limiting entries.

No packages required.

\documentclass{article}
%
% THIS CODE CAN \bubblesort A NUMBERED LIST AND THEN \compactthis LIST IN THE MANNER
% OF 1-3, 7, 11-13
%
\def\listterminator{9999}% SET TO *ANY* VALUE KNOWN NOT TO BE IN LIST (POSITIVE OR NEGATIVE)
\def\adjtie{, }
%\def\adjtie{--}% OPTIONAL IF 1--2 preferred over 1, 2
\newcommand\compactthis[1]{%
  \bubblesort{#1}%
  \expandafter\begincompaction\sortedlist,\listterminator,\relax%
}
\def\begincompaction#1,#2\relax{%
  \def\startlist{#1}%
  \def\currentendlist{#1}%
  \findendlist#2\relax%
}
\def\findendlist#1,#2\relax{%
  \ifnum\numexpr\currentendlist+1\relax=#1\relax%
    \def\currentendlist{#1}%
    \findendlist#2\relax%
  \else%
    \ifnum\startlist=\currentendlist\relax%
      \ignorespaces\startlist\unskip%
    \else%
      \ifnum\numexpr\startlist+1\relax=\currentendlist\relax%
        \ignorespaces\startlist\unskip\adjtie\ignorespaces\currentendlist\unskip%
      \else%
        \ignorespaces\startlist\unskip--\ignorespaces\currentendlist\unskip%
      \fi%
    \fi%
    \ifnum#1=\listterminator\else,\ \begincompaction#1,#2\relax\fi%
  \fi%
}
\newcommand\bubblesort[1]{\def\sortedlist{}\sortlist#1,\listterminator,\relax}
\def\sortlist#1,#2,#3\relax{%
  \ifnum#2=\listterminator\relax%
    \edef\sortedlist{\sortedlist#1}%
  \else
    \ifnum#1<#2\relax%
      \edef\sortedlist{\sortedlist#1,}%
      \sortlist#2,#3\relax%
    \else%
      \let\tmp\sortedlist%
      \def\sortedlist{}%
      \expandafter\sortlist\tmp#2,#1,#3\relax%
    \fi%
  \fi%
}
\begin{document}
Bubble Sort Demonstration: 
\bubblesort{1,2,11, 7, 4, 3}\sortedlist\par
\compactthis{1,2,3,4,5,7,8,9, 11}\par
\compactthis{1,2, 12 ,13 ,18, 20} (single member)\par
\compactthis{1,2, 12,13,18, 19, 20} (range at end)\par
\def\adjtie{--}\compactthis{1,2, 12,13,18, 19, 20} (\verb|\adjtie| set to {--})\par
\compactthis{1,2,11, 7, 4, 3, 12, 14, 13} (unsorted input)
\end{document}

enter image description here


Here is a LuaLaTeX solution. Because Lua has an easy-to-use sorting function (table.sort), it also sorts the input list. There are probably ways to make the Lua code more concise, but it should be easy to understand for anyone familiar with imperative programming languages.

\documentclass{article}
\usepackage{luacode}

\begin{luacode*}
function print_range(range_min, range_max)
  if range_min == range_max then
    tex.sprint(tostring(range_min))
  else
    tex.sprint(tostring(range_min) .. "--" .. tostring(range_max))
  end
end
function compactthis(...)
  local numbers = {...}
  table.sort(numbers)
  local range_started = false
  local range_min = 0
  local range_max = 0
  for i = 1, #numbers do
    if range_started then
      if numbers[i] <= range_max + 1 then
        range_max = numbers[i]
      else
        print_range(range_min, range_max)
        range_started = false
      end
    end
    if not range_started then
      if i ~= 1 then
        tex.sprint(", ")
      end
      range_started = true
      range_min = numbers[i]
      range_max = numbers[i]
    end
  end
  if range_started then
    print_range(range_min, range_max)
  end
end
\end{luacode*}
\newcommand\compactthis[1]{\luaexec{compactthis(#1)}}

\begin{document}
\compactthis{1,2,3,4,5,9,8,7,11}
\end{document}

Tags:

Macros