Sorting a comma-separated list with LaTeX?
How about Lua?
\def\sortlist#1{%
\directlua{%
local t = { #1 }
table.sort(t)
tex.sprint(table.concat(t,", "))
}%
}
\sortlist{"World", "Hello"}
\bye
Addendum: One of the really useful advantages of this approach is that it is fully expandable, meaning that after
\edef\x{\sortlist{"World", "Hello"}}
the macro \x
will contain the sorted list.
When using ConTeXt MKIV you can use the internal settings parser to convert a comma separated list to a Lua table. This has the advantage that you don't need to handle quotes yourself. It also respects grouping with commas inside.
\def\sortlist#1{%
\ctxlua{%
local t = utilities.parsers.settings_to_array("\luaescapestring{#1}")
table.sort(t)
tex.sprint(table.concat(t,", "))
}%
}
\starttext
\sortlist{World, Hello, {Entry, with, commas}, "Quotes"}
\stoptext
This seems to work, with no packages. EDITED to solve the upper/lower-case problem.
EDIT: Resolved problem when a comparison ran out of letters prior to resolving the order, for example, wash, washer
.
See ADDENDUM for handling (after a fashion) diacritics.
\documentclass{article}
\def\listterminator{;}
\makeatletter
\newcommand\alphabubblesort[1]{\def\presorted{}\def\sortedlist{}%
\sortlist#1,\listterminator,\relax}
\def\sortlist#1#2,#3#4,#5\relax{%
\if\listterminator#3#4\relax%
\edef\sortedlist{\sortedlist#1#2}%
\else
\ifnum\the\lccode`#1<\the\lccode`#3\relax%
\edef\sortedlist{\sortedlist\presorted#1#2, }%
\expandafter\def\expandafter\svfirst\expandafter{\presorted#3#4}%
\def\presorted{}%
\expandafter\sortlist\svfirst,#5\relax%
\else%
\ifnum`#1=`#3\relax%
\ifx\relax#2\relax%
\edef\sortedlist{\sortedlist\presorted#1#2, }%
\expandafter\def\expandafter\svfirst\expandafter{\presorted#3#4}%
\def\presorted{}%
\expandafter\sortlist\svfirst,#5\relax%
\else%
\ifx\relax#4\relax%
\edef\sortedlist{\sortedlist\presorted#3#4, }%
\expandafter\def\expandafter\svfirst\expandafter{\presorted#1#2}%
\def\presorted{}%
\expandafter\sortlist\svfirst,#5\relax%
\else
\g@addto@macro\presorted{#1}%
\sortlist#2,#4,#5\relax%
\fi%
\fi%
\else%
\let\tmp\sortedlist%
\def\sortedlist{}%
\expandafter\def\expandafter\svfirst\expandafter{\presorted#3#4}%
\expandafter\def\expandafter\svsecond\expandafter{\presorted#1#2}%
\def\presorted{}%
\expandafter\expandafter\expandafter\expandafter\expandafter%
\expandafter\expandafter\sortlist\expandafter\expandafter%
\expandafter\tmp\expandafter\svfirst\expandafter,\svsecond,#5\relax%
\fi%
\fi%
\fi%
}
\makeatother
\begin{document}
\alphabubblesort{book, boot, boat,toad,attic,wish,wash,wasn't,Cat ,Xylophone}
\sortedlist\par
\alphabubblesort{book, washer, boot, boat,toad,attic,wish,wash,wasn't,Cat ,Xylophone}
\sortedlist\par
\end{document}
ADDENDUM
Here's a version that can handle diacritics, in the sense that they do not break the algorithm. This was accomplished by changing the \edef
s in the above algorithm to appropriately expanded \def
s.
However, diacritics here will always precede all non-diacritic letters in the sort. While maybe not the ideal behavior, it may still be useful.
\documentclass{article}
\def\listterminator{;}
\makeatletter
\newcommand\alphabubblesort[1]{\def\presorted{}\def\sortedlist{}%
\sortlist#1,\listterminator,\relax}
\def\sortlist#1#2,#3#4,#5\relax{%
\if\listterminator#3#4\relax%
\expandafter\def\expandafter\sortedlist\expandafter{\sortedlist#1#2}%
\else
\ifnum\the\lccode`#1<\the\lccode`#3\relax%
\expandafter\expandafter\expandafter\def\expandafter\expandafter%
\expandafter\sortedlist\expandafter\expandafter\expandafter{%
\expandafter\sortedlist\presorted#1#2, }%
\expandafter\def\expandafter\svfirst\expandafter{\presorted#3#4}%
\def\presorted{}%
\expandafter\sortlist\svfirst,#5\relax%
\else%
\ifnum`#1=`#3\relax%
\ifx\relax#2\relax%
\expandafter\expandafter\expandafter\def\expandafter\expandafter%
\expandafter\sortedlist\expandafter\expandafter\expandafter{%
\expandafter\sortedlist\presorted#1#2, }%
\expandafter\def\expandafter\svfirst\expandafter{\presorted#3#4}%
\def\presorted{}%
\expandafter\sortlist\svfirst,#5\relax%
\else%
\ifx\relax#4\relax%
\expandafter\expandafter\expandafter\def\expandafter\expandafter%
\expandafter\sortedlist\expandafter\expandafter\expandafter{%
\expandafter\sortedlist\presorted#3#4, }%
\expandafter\def\expandafter\svfirst\expandafter{\presorted#1#2}%
\def\presorted{}%
\expandafter\sortlist\svfirst,#5\relax%
\else
\g@addto@macro\presorted{#1}%
\sortlist#2,#4,#5\relax%
\fi%
\fi%
\else%
\let\tmp\sortedlist%
\def\sortedlist{}%
\expandafter\def\expandafter\svfirst\expandafter{\presorted#3#4}%
\expandafter\def\expandafter\svsecond\expandafter{\presorted#1#2}%
\def\presorted{}%
\expandafter\expandafter\expandafter\expandafter\expandafter%
\expandafter\expandafter\sortlist\expandafter\expandafter%
\expandafter\tmp\expandafter\svfirst\expandafter,\svsecond,#5\relax%
\fi%
\fi%
\fi%
}
\makeatother
\begin{document}
\alphabubblesort{book, washer, w\"asher, boot, boat,toad,attic,wish,wash,wasn't,
Cat, Xylophone, w\"ash, edifice, \'edifice, w\"asherei}
\sortedlist\par
\end{document}
The algorithm was based on my \bubblesort
macro here: Using LaTeX to compact a list of numbers
You can safely compare strings consisting of ASCII characters; words with accented characters will not work.
\documentclass{article}
\usepackage{expl3,l3sort,xparse}
\ExplSyntaxOn
\prg_new_conditional:Nnn \john_string_if_before:nn { p,T,F,TF }
{% I hope the LaTeX3 police won't catch me
\int_compare:nTF { \pdftex_strcmp:D { #1 } { #2 } < 0 }
{
\prg_return_true:
}
{
\prg_return_false:
}
}
\NewDocumentCommand{\sortlist}{smm}
{
\IfBooleanTF{#1}
{
\clist_set:No \l__john_sortlist_data_clist { #2 }
}
{
\clist_set:Nn \l__john_sortlist_data_clist { #2 }
}
\john_sortlist:N \l__john_sortlist_data_clist
\clist_set_eq:NN #3 \l__john_sortlist_data_clist
}
\clist_new:N \l__john_sortlist_data_clist
\cs_new_protected:Nn \john_sortlist:N
{
\clist_sort:Nn #1
{
\john_string_if_before:nnTF { ##1 } { ##2 }
{
\sort_return_same:
}
{
\sort_return_swapped:
}
}
}
\ExplSyntaxOff
\begin{document}
\sortlist{World,Hello}{\mylistA}
\mylistA
\newcommand{\mylistB}{duck,cat,dog}
\sortlist*{\mylistB}{\mylistC}
\mylistB ${}\to{}$\mylistC
\end{document}
If you want that the comparison is case insensitive, change the definition of \john_sortlist:N
into
\cs_new_protected:Nn \john_sortlist:N
{
\clist_sort:Nn #1
{
\john_string_if_before:nnTF { \str_foldcase:n {##1} } { \str_foldcase:n {##2} }
{
\sort_return_same:
}
{
\sort_return_swapped:
}
}
}