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.

A.P.
  • 734
  • 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 Sep 24 '20 at 07: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. Sep 24 '20 at 07: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. Sep 24 '20 at 07:51
  • Would a LuaLaTeX-based solution be of interest? – Mico Sep 24 '20 at 08:00
  • @Mico Unfortunately, I don't think I would be able to switch to LuaLaTeX at this stage, since the document is almost finished (and quite large). However, feel free to post an answer based on LuaLaTeX for completeness. – A.P. Sep 24 '20 at 08:21
  • Your input is seperated by , and and? and your output sort by what? Your example is a mess for me. – ZhiyuanLck Sep 24 '20 at 08:25
  • @ZhiyuanLck I used the same syntax of the bibTeX/bibLaTeX author field: each name in the input has the form <surname>, <forenames>, and the names are separated by and. As stated in the title, the output should be sorted alphabetically (on the surnames). This looks rather clean to me... – A.P. Sep 24 '20 at 08:32
  • There is no need to output a list environment, and the changes are trivial - see my answer. – bers Sep 24 '20 at 09:09
  • You might explain why you are not looking for sort an itemized list. Since this is a thesis, which is usually a one-time effort, one may wonder why you cannot sort this list yourself (but I do get that one might not want to re-do then when adding names). However, it is unclear to me if, and if so, why, the input format is fixed as a single string. – bers Sep 24 '20 at 10:57
  • 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 Sep 26 '20 at 05:15

4 Answers4

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

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}

ZhiyuanLck
  • 4,516
  • It will take me quite a while to properly understand your code, but it seems to work as expected. Thanks! – A.P. Sep 24 '20 at 10:07
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}
Mico
  • 506,678
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.

bers
  • 5,404
  • 1
    How is the question not complete? – A.P. Sep 24 '20 at 09: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 Sep 24 '20 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 Sep 24 '20 at 10:55