Sorting Table Column

The datatool package has quite a lot of functions to sort and analyse data.


I have no doubt that datatool is the way to go here. However, it is possible to use the code from my answer to the sorting question to do this.

You need to define a new comparator that compares only the first column. This particular implementation wants something that has a top level expansion into a row. E.g., you can define a macro for each row.

\def\rowi  {C & foo & qwer \\}
\def\rowii {A & bar & asdf \\}
\def\rowiii{B & baz & zxcv \\}

The goal is to be able to write the following to get sorted rows.

\begin{tabular}{lll}
\toprule
Letters & Metasyntatic variables & Gibberish\\
\midrule
\sortfirstcol{\rowi\rowii\rowiii}
\bottomrule
\end{tabular}

In particular, the \sortfirstcol{\rowi\rowii\rowiii} should expand to the sorted rows. As with \sortalpha, \sortfirstcol is just a simple macro that expands \sort with the comparator \sort@firstcol as its first argument.

\newcommand*\sortfirstcol{\sort\sort@firstcol} 

The comparator isn't extremely complicated, but it does need to extract the first column of each argument. For that, we use a helper macro. And in keeping with the spirit of the original answer, this version is expandable.

\def\sort@firstcol#1#2{%
    \expandafter\expandafter\expandafter
        \sort@fc@helper\expandafter#1#2#1#2%
}

\def\sort@fc@helper#1&#2\\#3&#4\\#5#6#7{%
    \ifnumcomp{\pdfstrcmp{#1}{#3}}<{0}
    {#7{#5}{#6}}% else
    {#7{#6}{#5}}%
}

The \expandafters are going to expand the first two arguments a single time. For example, if the comparator is "called" as \sort@firstcol{\rowi}{\rowii}{...}, then it will expand to

\sort@fc@helper C & foo & qwer \\A & bar & asdf \\\rowi\rowii{...}

and so #1 will be the tokens "C ", #3 will be "A ", #5 will be \rowi, #6 will be \rowii, and lastly #7 will be the {...} that is the continuation that gets passed to the comparator.

From here, it's identical to \sort@alpha: it compares #1 and #3, passing #5 and #6 to the continuation.

Lastly, due to the representation of the rows, the \sortend and \sortoutput macros should be set to just expand to their argument:

\newcommand*\sortoutput[1]{#1}
\newcommand*\sortend[1]{#1}

The following is a complete example using the above code and the code from the previous answer.

\documentclass{article}
\usepackage{etoolbox}
\usepackage{booktabs}
\makeatletter
% #1 - comparator
% #2 - token list to sort
\newcommand\sort[2]{%
        \ifstrempty{#2}
        {}% else
        {%
                \sort@begin#1{}#2\sort@s\sort@begin
        }%
}

% helpers
\def\sort@s{\sort@s}
\def\ifsort@s#1{%
        \ifx\sort@s#1%
                \expandafter\@firstoftwo
        \else
                \expandafter\@secondoftwo
        \fi
}

% #1 - comparator
% #2 - tokens processed so far
% #3 - smallest token so far
% #4 - rest of the list
\def\sort@begin#1#2#3#4\sort@begin{%
        \ifsort@s{#4}
        {%
                \sortend{#3}%
                \sort#1{#2}%
        }% else
        {%
                \sort@go#1{#2}{#3}#4\sort@go
        }%
}

%     #1 - comparator
% #2 - tokens processed so far
% #3 - smallest token so far
% #4 - token under consideration
% #5 - rest of the list
\def\sort@go#1#2#3#4#5\sort@go{%
        #1{#3}{#4}{\sort@output#1{#2}{#5}}%
}
% #1 - comparator
% #2 - tokens processed so far
% #3 - rest of the list
% #4 - smaller of the two tokens
% #5 - larger of the two tokens
\def\sort@output#1#2#3#4#5{%
        \ifsort@s{#3}
        {%
                \sortoutput{#4}%
                \sort#1{#2{#5}}%
        }% else
        {%
                \sort@begin#1{#2{#5}}{#4}#3\sort@begin
        }%
}

\def\sort@numlt#1#2#3{%
        \ifnumcomp{#1}<{#2}
        {#3{#1}{#2}}% else
        {#3{#2}{#1}}%
}

\def\sort@numgt#1#2#3{%
        \ifnumcomp{#1}>{#2}
        {#3{#1}{#2}}% else
        {#3{#2}{#1}}%
}

\def\sort@alpha#1#2#3{%
        \ifnumcomp{\pdfstrcmp{#1}{#2}}<{0}
        {#3{#1}{#2}}% else
        {#3{#2}{#1}}%
}

\def\sort@firstcol#1#2{%
    \expandafter\expandafter\expandafter
        \sort@fc@helper\expandafter#1#2#1#2%
}

\def\sort@fc@helper#1&#2\\#3&#4\\#5#6#7{%
    \ifnumcomp{\pdfstrcmp{#1}{#3}}<{0}
    {#7{#5}{#6}}% else
    {#7{#6}{#5}}%
}

\newcommand*\sortnumlt{\sort\sort@numlt}
\newcommand*\sortnumgt{\sort\sort@numgt}
\newcommand*\sortalpha{\sort\sort@alpha}
\newcommand*\sortfirstcol{\sort\sort@firstcol}
\makeatother

% Change these to change out the sort outputs.
\newcommand*\sortoutput[1]{#1}
\newcommand*\sortend[1]{#1}

\begin{document}

\def\rowi  {C & foo & qwer \\}
\def\rowii {A & bar & asdf \\}
\def\rowiii{B & baz & zxcv \\}

\begin{tabular}{lll}
\toprule
Letters & Metasyntatic variables & Gibberish\\
\midrule
\sortfirstcol{\rowi\rowii\rowiii}
\bottomrule
\end{tabular}

\end{document}

Finally, I should point out that this representation (that is, rows as macros) isn't necessary. Due to the way \sort processes its arguments, one could easily write a comparator that allowed one to write the following.

\sortfirstcol{%
    {C & foo & qwer}%
    {A & bar & asdf}%
    {B & baz & zxcv}%
}
\def\sortoutput#1{#1\\}
\def\sortend#1{#1\\}

Perhaps I should have done that. The comparator would be slightly simpler.

Also, as implemented, a space at the beginning or end of the first column matters. One could probably deal with that too.

Tags:

Tables