5

I have a long number of people to acknowledge in my thesis, and I was wondering if it would be possible to sort their names automatically, rather than doing it by hand. In other words, I would like a macro, say \sorted, which would take

\sorted{Gauss, Carl Friedrich and Riemann, Bernhard and Euler, Leonhard}

and output

Leonard Euler, Carl Friedrich Gauss and Bernhard Riemann

To be clear, I am not looking to sort the items in a list environment. Rather, I would like to output a sentence that contains the various names, in alphabetical order. All names but the last two should be separated by a ,, while the last two should be separated by and.

10
  • See the question above. You might only need to change \begin{itemize}/\end{itemize} (to nothing) and \item (to a comma, largely) within \newenvironment{sortedlist}.
    – bers
    Commented Sep 24, 2020 at 7:24
  • @bers Not at all. I said "list" for lack of better words, but I do not want to output a list environment. Rather, I want a sentence listing the names, like in the sample output.
    – A.P.
    Commented Sep 24, 2020 at 7:41
  • @bers If it is possible to modify the answer to that question to fit my requirement, then the necessary changes do not trivial at all to me. In that case, could you please write an answer instead of closing this question as a duplicate?
    – A.P.
    Commented Sep 24, 2020 at 7:51
  • Would a LuaLaTeX-based solution be of interest?
    – Mico
    Commented Sep 24, 2020 at 8:00
  • 1
    I'm voting to reopen this posting as it is NOT a duplicate of the earlier posting, Alphabetically display the items in itemize. The earlier posting concerned the sorting of items in an itemize environment; that is decidedly not the setup of the current query.
    – Mico
    Commented Sep 26, 2020 at 5:15

4 Answers 4

2

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}
1
  • It will take me quite a while to properly understand your code, but it seems to work as expected. Thanks!
    – A.P.
    Commented Sep 24, 2020 at 10:07
3

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

2

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

This is not a complete answer, but the question is not complete, either. As in my comment above, I am basing this answer on this question: Alphabetically display the items in itemize

I changed three lines in the preamble: in essence, there is no need to output a list, just because you input as list. I think that was your biggest issue with my comment.

\documentclass{article}
\usepackage{datatool}% http://ctan.org/pkg/datatool
\newcommand{\sortitem}[1]{%
  \DTLnewrow{list}% Create a new entry
  \DTLnewdbentry{list}{description}{#1}% Add entry as description
}
\newenvironment{sortedlist}{%
  \DTLifdbexists{list}{\DTLcleardb{list}}{\DTLnewdb{list}}% Create new/discard old list
}{%
  \DTLsort{description}{list}% Sort list
  %\begin{itemize}% THIS LINE CHANGED
    \DTLforeach*{list}{\theDesc=description}{%
      \theDesc{} and }% THIS LINE CHANGED
  %\end{itemize}% THIS LINE CHANGED
}
\begin{document}

\begin{sortedlist}
    \sortitem{Gauss, Carl Friedrich}
    \sortitem{Riemann, Bernhard}
    \sortitem{Euler, Leonhard}
\end{sortedlist}

\end{document}

This outputs

Euler, Leonhard and Gauss, Carl Friedrich and Riemann, Bernhard and

Your remaining, implicit parts of question (output list separated by commas and "and") are probably answered in Iterating through comma-separated arguments or Special handling of first and/or last item in an etoolbox list.

3
  • 1
    How is the question not complete?
    – A.P.
    Commented Sep 24, 2020 at 9:48
  • @A.P. Well, the question is "Alphabetical sorting of a sequence of names". But its content is really about parsing a text string into a sequence, then sorting this sequence, and then outputting the sequence as a string again. I do answer the part on sorting (which is the part addressed in the question title), but not the other two.
    – bers
    Commented Sep 24, 2020 at 10:54
  • @A.P. In addition, I stand by my "duplicate" vote since each of these three sub-questions are addressed in other questions, and I see no attempt of yours to combine, or even try them.
    – bers
    Commented Sep 24, 2020 at 10:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .