Alphabetical sorting of a sequence of names

Bubble sorter, which I adapt from my modification to David's answer to my question at Trying to eliminate stack overflow during recursion.

The \sortlist macro is the bubble sorter (from the referenced answer, but with and rather than , as the list seperator). However, it leaves the result in the form of Last Name, First and ....

I had to add the \rework macro to make it First Last Name and employ \whichsep to choose whether a , or and should be inserted between names, depending on their placement in the list.

No packages required!

\documentclass[10pt]{article}
\newcommand\alphabubblesort[1]{\def\sortedlist{}%
  \expandafter\sortlist#1 and \cr and \relax
  \expandafter\rework\sortedlist and \relax}
\def\sortlist#1and #2and #3\relax{%
  \let\next\relax
  \ifx\cr#2\relax%
    \edef\sortedlist{\sortedlist#1}%
  \else
    \picknext#1!and #2!\relax%
    \if F\flipflop%
      \edef\sortedlist{\sortedlist#1and }%
      \def\next{\sortlist#2and #3\relax}%
    \else%
      \let\tmp\sortedlist%
      \def\sortedlist{}%
      \def\next{\expandafter\sortlist\tmp#2and #1and #3\relax}%
    \fi%
  \fi%
\next
}
\def\picknext#1#2and #3#4\relax{%
  \ifnum\the\lccode`#1<\the\lccode`#3\relax
    \xdef\flipflop{F}%
  \else%
    \ifnum\the\lccode`#1>\the\lccode`#3\relax%
      \xdef\flipflop{T}%
    \else%
      \ZZfifi{\picknext#2!and #4!\relax}%
    \fi%
  \fi%
}
\def\ZZfifi#1\fi\fi{\fi\fi#1}
\def\rework#1, #2and #3\relax{#2#1\ifx\relax#3\relax\else
  \whichsep#3,\relax\rework#3\relax\fi}
\def\whichsep#1,#2,#3\relax{\ifx\relax#3\relax\ and \else, \fi}
\begin{document}
\def\mydata{%
Gauss, Carl Friedrich and Riemann, Bernhard and Euler, Leonhard}
\alphabubblesort{\mydata}

I wish to thank 
\alphabubblesort{%
Gauss, Carl Friedrich and 
Riemann, Bernhard and 
Euler, Leonhard and 
Bach, Carl Philipp Emanuel and 
Dumbledore, Albus Percival Wulfric Brian and 
Granger, Hermione Jean and 
Scott Thomas, Kristin and 
Van Gogh, Vincent and 
Sartre, Jean-Paul and 
Toulouse-Lautrec, Henri de}
for their valuable comments and incisive critiques.
\end{document}

enter image description here


Is this what you want? I define some ordering rule:

  • aA: [a-zA-Z]
  • raA: [Z-Az-a]
  • Aa: [A-Za-z]
  • rAa: [z-aZ-A]

You can also define your owner ordering rule.

enter image description here

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\tl_new:N \l__seq_sep_tl
\seq_new:N \l__alph_seq
\seq_new:N \l__Alph_seq
\seq_new:N \l__Alphalpa_seq
\seq_new:N \l__alphAlph_seq
\seq_new:N \l__ralph_seq
\seq_new:N \l__rAlph_seq
\seq_new:N \l__rAlphalpa_seq
\seq_new:N \l__ralphAlph_seq
\seq_new:N \l__result_seq
\seq_new:N \l__custom_order_seq
\bool_new:N \l__if_less_bool
\prop_new:N \l__order_prop

\seq_set_from_clist:Nn \l__alph_seq
  { a,b,c,d,e,f,g,h,i,g,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z }
\seq_set_from_clist:Nn \l__Alph_seq
  { A,B,C,D,E,F,G,H,I,G,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z }
\seq_set_from_clist:Nn \l__Alphalph_seq
  {
    A,B,C,D,E,F,G,H,I,G,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,
    a,b,c,d,e,f,g,h,i,g,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
  }
\seq_set_from_clist:Nn \l__alphAlph_seq
  {
    a,b,c,d,e,f,g,h,i,g,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,
    A,B,C,D,E,F,G,H,I,G,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z
  }
\seq_set_eq:NN \l__ralph_seq \l__alph_seq
\seq_set_eq:NN \l__rAlph_seq \l__Alph_seq
\seq_set_eq:NN \l__ralphAlph_seq \l__alphAlph_seq
\seq_set_eq:NN \l__rAlphalph_seq \l__Alphalph_seq
\seq_reverse:N \l__ralph_seq
\seq_reverse:N \l__rAlph_seq
\seq_reverse:N \l__ralphAlph_seq
\seq_reverse:N \l__rAlphalph_seq
\seq_set_eq:NN \l__custom_order_seq \l__Alphalph_seq

\prop_set_from_keyval:Nn \l__order_prop
  {
    a = alph,
    A = Alph,
    aA = alphAlph,
    Aa = Alphalph,
    ra = ralph,
    rA = rAlph,
    raA = ralphAlph,
    rAa = rAlphalph,
  }

\keys_define:nn { sort }
  {
    order .code:n = { \set_order_from_option:n { #1 } },
    sep .tl_set:N = \l__seq_sep_tl,
  }

\cs_new_protected:Nn \set_sort_order_from_seq:N
  {
    \int_zero:N \l_tmpa_int
    \seq_remove_duplicates:N #1
    \seq_map_inline:Nn #1
      {
        \int_incr:N \l_tmpa_int
        \int_if_exist:cF { g__sort_##1 }
          {
            \int_new:c { g__sort_##1 }
          }
        \int_gset_eq:cN { g__sort_##1 } \l_tmpa_int
      }
  }

\prg_new_protected_conditional:Nnn \str_if_less:nn { T, F, TF }
  {
    \int_set:Nn \l_tmpa_int
      { \str_count_ignore_spaces:n { #1 } }
    \int_set:Nn \l_tmpb_int
      { \str_count_ignore_spaces:n { #2 } }
    \int_compare:nTF { \l_tmpa_int < \l_tmpb_int }
      { \bool_set_true:N \l__if_less_bool }
      { \bool_set_false:N \l__if_less_bool }
    \int_step_inline:nn { \int_min:nn { \l_tmpa_int } { \l_tmpb_int } }
      {
        \int_set_eq:Nc \l_tmpa_int
          { g__sort_\str_item:nn { #1 } { ##1 } }
        \int_set_eq:Nc \l_tmpb_int
          { g__sort_\str_item:nn { #2 } { ##1 } }
        \int_compare:nF { \l_tmpa_int = \l_tmpb_int }
          {
            \int_compare:nTF { \l_tmpa_int < \l_tmpb_int }
              { \bool_set_true:N \l__if_less_bool }
              { \bool_set_false:N \l__if_less_bool }
            \prg_break:
          }
      }
    \bool_if:NTF \l__if_less_bool
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1 seq to be sorted #2 predefined order seq
\cs_new_protected:Nn \seq_sort_by_order:NN
  {
    \set_sort_order_from_seq:N #2
    \seq_sort:Nn #1
      {
        \str_if_less:nnTF { ##1 } { ##2 }
          { \sort_return_same: }
          { \sort_return_swapped: }
      }
  }


\cs_generate_variant:Nn \seq_set_split:Nnn { Nxo }
\cs_new_protected:Nn \sort_custom_seq:nn
  {
    \keys_set:nn { sort }
      {
        sep = {,},
        #1
      }
    \seq_set_split:Nxo \l__result_seq { \l__seq_sep_tl } { #2 }
    \seq_sort_by_order:NN \l__result_seq \l__custom_order_seq
  }

% #1 seq handle function #2 options #3 list
\cs_new_protected:Nn \sort_custom_seq:Nnn
  {
    \sort_custom_seq:nn { #2 } { #3 }
    #1 \l__result_seq
  }

\cs_new_protected:Nn \my_transpose:N
  {
    \seq_clear_new:N \l__new_seq
    \seq_map_inline:Nn #1
      {
        \seq_clear_new:N \l__item_seq
        \seq_set_split:Nnn \l__item_seq { , } { ##1 }
        \seq_reverse:N \l__item_seq
        \seq_put_right:Nx \l__new_seq { \seq_use:Nn \l__item_seq { ~ } }
      }
    \seq_set_eq:NN #1 \l__new_seq
  }

\cs_new_protected:Nn \set_order_from_seq:nn
  {
    \seq_set_split:Nnn \l__custom_order_seq { #1 } { #2 }
  }

\cs_new_protected:Nn \set_order_from_str:n
  {
    \str_set:Nn \l_tmpa_str { #1 }
    \seq_clear:N \l__custom_order_seq
    \str_map_inline:Nn \l_tmpa_str
      {
        \seq_put_right:Nn \l__custom_order_seq { ##1 }
      }
  }

\cs_new_protected:Nn \set_order_from_option:n
  {
    \prop_if_in:NnTF \l__order_prop { #1 }
      {
        \seq_set_eq:Nc \l__custom_order_seq
          { l__\prop_item:Nn \l__order_prop { #1 }_seq }
      }
      {
        \set_order_from_str:n { #1 }
      }
  }

\NewDocumentCommand { \setorder } { o m }
  {
    \IfNoValueTF { #1 }
      { \set_order_from_str:n { #2 } }
      { \set_order_from_seq:nn { #1 } { #2 } }
  }

\NewDocumentCommand { \mysorted } { O{} +m }
  {
    \sort_custom_seq:Nnn \my_transpose:N { #1 } { #2 }
    \seq_use:Nnnn \l__result_seq { ~and~ } { ,~ } { ~and~ }
  }

\NewDocumentCommand { \sorted } { m +m }
  {
    \sort_custom_seq:nn { order = #1 } { #2 }
    \makebox[4cm][l]{\bfseries Order:~#1}
    \seq_map_inline:Nn \l__result_seq
      {
        \makebox[1.2cm][l]{##1}
      }
  }

\ExplSyntaxOff

\begin{document}
\mysorted[sep=and]{Gauss, Carl Friedrich and Riemann, Bernhard and Euler, Leonhard}

\def\test{app, band, apple, Apple, App}
\sorted{aA}{\test}

\sorted{raA}{\test}

\sorted{Aa}{\test}

\sorted{rAa}{\test}

\sorted{ab-+*@c}{abc, c@-, b+@, @cb, b-c}
\end{document}

Here's a LuaLaTeX based solution. It sets up a LaTeX macro called \sorted, which calls a Lua function called sorted to do most of the work. The word and is taken to be the keyword that separates persons, while , (comma) is the separator between the surname and given-name portions of a full name. Space characters and hyphen characters are allowed in both the given-name and first-name portions of a full name.

enter image description here

% !TeX program = lualatex
\documentclass{article}

%% Lua-side code
\usepackage{luacode} % for 'luacode' environment
\begin{luacode}
function string_to_table ( str )
   local namelist = {} -- initialize the table
   str:gsub ( "([^;]*)" , function ( name ) 
        -- Strip off any leading and trailing whitespace:
        name = name:gsub ( "^%s*(.-)%s*$" , "%1" )
        -- Insert 'name' in 'namelist'
        table.insert ( namelist , name )   
        end )
   return namelist
end

function sorted ( s )
   local t
   -- Change the separator keyword "and" to ";"
   s = s:gsub ( "and" , ";" )
   -- Convert to a Lua table:
   t = string_to_table ( s )
   -- Sort the table entries alphabetically:
   table.sort ( t )
   n = #t -- Retrieve number of entries
   -- Change "Surname, FirstName" to "FirstName Surname":
   for i=1,n do
     t[i] = string.gsub ( t[i] , "([%a%s%-]+)%,%s?(.+)" , "%2 %1" )
   end
   -- Output a string, using "and" as the final separator
   s = t[1]
   for i = 2,n-1 do s = s .. ", " .. t[i] end
   s = s .. " and " .. t[n]
   tex.sprint ( s )
end

\end{luacode}
%% LaTeX-side code:
\newcommand\sorted[1]{\directlua{sorted(\luastringN{#1})}}

\begin{document}
I wish to thank 
\sorted{Gauss, Carl Friedrich and Riemann,Bernhard and Euler, Leonhard and 
Bach, Carl Philipp Emanuel and Dumbledore,Albus Percival Wulfric Brian and 
Granger, Hermione Jean and Scott Thomas, Kristin and Van Gogh, Vincent and 
Sartre, Jean-Paul and Toulouse-Lautrec, Henri de}
for their valuable comments and incisive critiques.
\end{document}