# Using LaTeX to compact a list of numbers

This a bit out of ordinary. But has anybody used LaTeX to compact a list of numbers?

The closest example that comes to mind is the way the citation numbers are compressed and sorted using the cite package. So, if I write \cite{key1, key2, key3, key4, key5}, we get something like 1-3, 7, 9.

The situation under consideration is almost similar, except the numbers are real numbers or expanded to real numbers, not citation keys. So, if I write \compactthis{1,2,3,4,5,9,8,7, 11}, I will get 1-5, 7-9, 11.

Is there a quick way of achieving this? Writing macros from scratch will take time which I can not afford at this moment.

(I understand that programming languages are better suited for this, but my current situation demands the use of LaTeX.)

## 3 Answers

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}


• A comprehensive solution ready-to-use. – Masroor Aug 23 '16 at 17:35
• @Masroor Thank you. I appreciate the consideration and endorsement. – Steven B. Segletes Aug 23 '16 at 17:37

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

## 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}

• Highly procedural approach :) I'm looking forward to someone posting a functional solution (I'm really not able to do that myself) – yo' Aug 23 '16 at 9:39
• @yo': Yep, but gets the job done. :-) – Peter Grill Aug 23 '16 at 10:45
• It wasn't criticism! It was just ... well ... a comment; I'd really like to see a functional answer... – yo' Aug 23 '16 at 11:45
• Also, I'd not "compactify" 1, 2 to 1--2, but that's a matter of taste. – yo' Aug 23 '16 at 11:47

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}