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

enter image description here

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

enter image description here


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}

enter image description here

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 \edefs in the above algorithm to appropriately expanded \defs.

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}

enter image description here

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}

enter image description here

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