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