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}
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.
\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.
% !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}