# On sorting a string with multiple entries

In my LaTeX-file I create a variable \authors which could have content:

[Doe] John Doe; [Potter] Harry Potter; [Clinton] Bill Clinton; [Obama] Barack Obama; ...


This is dynamically created, so I could vary it a bit (I've got a data-file from which I retreive the first and last names). Is there a way I could turn this variable-text into the ordered (by last name) and neatly-displayed list

Bill Clinton, John Doe, Barack Obama, Harry Potter,...

1) is this possible, 2) should I store this initial information in a variable or should I store them in a file or something else?

• You may want to have a look at the datatool package, which can sort data and allow you to iterate through it. Dec 11 '14 at 16:14

Here is an implementation using expl3 and the module l3sort:

\documentclass{article}

\usepackage{xparse,l3sort,pdftexcmds}

\ExplSyntaxOn

\cs_set_eq:Nc \konewka_strcmp:nn { pdf@strcmp }

{
\IfNoValueTF{#1}
{
\konewka_add_author:nnn { #3 } { #2 } { #3 }
}
{
\konewka_add_author:nnn { #1 } { #2 } { #3 }
}
}

\NewDocumentCommand{\printauthors}{ }
{
\konewka_print_authors:
}

\seq_new:N \g_konewka_authors_id_seq
\seq_new:N \l__konewka_authors_full_seq

{
\seq_gput_right:Nn \g_konewka_authors_id_seq { #1 }
\prop_new:c { g_konewka_author_#1_prop }
\prop_gput:cnn { g_konewka_author_#1_prop } { fname } { #2 }
\prop_gput:cnn { g_konewka_author_#1_prop } { lname } { #3 }
}

\cs_new_protected:Npn \konewka_print_authors:
{
\seq_gsort:Nn \g_konewka_authors_id_seq
{
\string_compare:nnnTF {##1} {>} {##2} {\sort_reversed:} {\sort_ordered:}
}
\seq_clear:N \l__konewka_authors_full_seq
\seq_map_inline:Nn \g_konewka_authors_id_seq
{
\seq_put_right:Nx \l__konewka_authors_full_seq
{
\prop_item:cn { g_konewka_author_##1_prop } { fname }
\c_space_tl
\prop_item:cn { g_konewka_author_##1_prop } { lname }
}
}
\seq_use:Nn \l__konewka_authors_full_seq { ,~ }
}

\prg_new_conditional:Npnn \string_compare:nnn #1 #2 #3 {TF}
{
\if_int_compare:w \konewka_strcmp:nn {#1}{#3} #2 \c_zero
\prg_return_true:
\else:
\prg_return_false:
\fi
}

\ExplSyntaxOff

\begin{document}

\printauthors

\end{document}


An author is added with \addauthor{<first name(s)>}{<last name>}; an optional argument is allowed for coping with special characters; this optional argument will be used both for indexing the property lists and for sorting.

See Sort subsections alphabetically for another application of \seq_sort:Nn

If you have two authors with the same last name, use the optional argument; for example

\addauthor[Doe@Jane]{Jane}{Doe}


Note that the optional argument determines the sorting order; using @, which comes before letters in ASCII, will guarantee that the two Doe authors will be sorted before “Doeb”. One might use the same idea for all authors, that is, using “lname@fname” as sorting key, but this would give problems with special characters in the first name.

Here's a version that doesn't add an author if the sorting key (last name or optional argument) is already present in the database.

\documentclass{article}

\usepackage{xparse,l3sort,pdftexcmds}

\ExplSyntaxOn

\cs_set_eq:Nc \konewka_strcmp:nn { pdf@strcmp }

{
\IfNoValueTF{#1}
{
\konewka_add_author:nnn { #3 } { #2 } { #3 }
}
{
\konewka_add_author:nnn { #1 } { #2 } { #3 }
}
}

\NewDocumentCommand{\printauthors}{ }
{
\konewka_print_authors:
}

\seq_new:N \g_konewka_authors_id_seq
\seq_new:N \l__konewka_authors_full_seq

\msg_new:nnn { konewka/authors } { author~exists }
{
The ~ author ~ #1 ~ already ~ exists; ~ it ~ won't ~ be ~ added ~ again
}

{
\prop_if_exist:cTF { g_konewka_author_#1_prop }
{
\msg_warning:nnn { konewka/authors } { author~exists } { #1 }
}
{
\seq_gput_right:Nn \g_konewka_authors_id_seq { #1 }
\prop_new:c { g_konewka_author_#1_prop }
\prop_gput:cnn { g_konewka_author_#1_prop } { fname } { #2 }
\prop_gput:cnn { g_konewka_author_#1_prop } { lname } { #3 }
}
}

\cs_new_protected:Npn \konewka_print_authors:
{
\seq_gsort:Nn \g_konewka_authors_id_seq
{
\string_compare:nnnTF {##1} {>} {##2} {\sort_reversed:} {\sort_ordered:}
}
\seq_clear:N \l__konewka_authors_full_seq
\seq_map_inline:Nn \g_konewka_authors_id_seq
{
\seq_put_right:Nx \l__konewka_authors_full_seq
{
\prop_item:cn { g_konewka_author_##1_prop } { fname }
\c_space_tl
\prop_item:cn { g_konewka_author_##1_prop } { lname }
}
}
\seq_use:Nn \l__konewka_authors_full_seq { ,~ }
}

\prg_new_conditional:Npnn \string_compare:nnn #1 #2 #3 {TF}
{
\if_int_compare:w \konewka_strcmp:nn {#1}{#3} #2 \c_zero
\prg_return_true:
\else:
\prg_return_false:
\fi
}

\ExplSyntaxOff

\begin{document}

\printauthors

\end{document}


Running this file will produce a warning such as

*************************************************
* konewka/authors warning: "author exists"
*
*************************************************


Change \msg_warning:nnn into \msg_error:nnn if you prefer an error is raised rather than a warning is issued.

• Awesome, this works great! Dec 11 '14 at 22:49
• I'm now compiling the code on another computer (at my university) and I'm getting the error ! Undefined control sequence. <argument> \prop_item:cn {g_konewka_author_Clinton_prop}{fname}.... 1.74 \printauthors and several others (two for each author, both after \prop_item:cn) Have you got any idea where this can come from? Dec 12 '14 at 9:31
• @konewka Outdated version of expl3 Dec 12 '14 at 9:47
• Changing \prop_item:cn to \prop_get:cn solved the problem as well, and the script had a hard time dealing with two people with the same last name, so I changed all the prop-names from #1_prop to #1_#2_prop Dec 12 '14 at 9:52
• I added some notes about sorting and also a version that doesn't add the same author twice. Dec 12 '14 at 10:28

Just for illustration I show how to solve this task in plain TeX using OPmac macro where mergesort is implemented.

\input opmac

\def\sort{\begingroup\setprimarysorting\def\iilist{}\sortA}
\def\sortA#1#2{\ifx\relax#1\sortB\else
\expandafter\preparesorting\csname,#1\endcsname
\expandafter\edef\csname,#1\endcsname{{\tmpb}{#2}}%
\expandafter\sortA\fi
}
\def\sortB{\def\message##1{}\dosorting
\def\act##1{\ifx##1\relax\else \seconddata##1\sortC \expandafter\act\fi}%
\gdef\tmpb{}\expandafter\act\iilist\relax
\endgroup
}

\def\printauthors{\def\tmp{}\expandafter\printauthorsA\authors [] {} {}; }
\def\printauthorsA [#1] #2 #3; {%
\ifx^#1^\expandafter\sort\tmp\relax\relax
\def\tmp{}\expandafter\printauthorsB\tmpb\relax