17

I've been very impressed with xparse and expl3, and have written a wonderful document command using \ProcessList that takes a comma separated list and turns it into a (basically) 1 column table.

\stacky{A,B,C,D}
-> \begin{tabular}{rr}
   \tiny 1st & A \\
   \tiny 2nd & B \\
   \tiny 3rd & C \\
   \tiny 4th & D
   \end{tabular}

Now I've become mad with the power and want to specify more than one column at a time. Something like:

\stacksy{ 5: A,B,C,D ; 6: A,C,D,B ; 3: C,A,B,D }
-> \begin{tabular}{rrrr}
             & 5 & 6 & 3 \\ \hline
   \tiny 1st & A & A & C \\
   \tiny 2nd & B & C & A \\
   \tiny 3rd & C & D & B \\
   \tiny 4th & D & B & D \\
   \end{tabular}

Everything is fine, except that the input is in column-major order, but TeX (or the tabular environment) expects row-major order.

How do I transpose the input list of lists?

David Carlisle
  • 757,742
Jack Schmidt
  • 1,137
  • Not an answer, but it's important to note that xparse is about 'parsing' in the sense 'reading LaTeX2e command syntax' rather than 'parsing arbitrary input'. – Joseph Wright Sep 05 '12 at 16:13
  • @JosephWright: Does it sound right that I should be using the sequence splits and maps from expl3 instead of ProcessList from xparse? Scott's solution is working very well, and I'll likely rewrite my single-stack version to use those ideas. – Jack Schmidt Sep 05 '12 at 18:49
  • No, as what you are doing is defining an input syntax. I'm simply flagging up that \ProcessList and the like can only take you so far: we've been asked before about extremely complex input, which is really beyond the scope of xparse. Currently, so are nested lists (indeed, I'd have to think hard about a suitable syntax!). – Joseph Wright Sep 05 '12 at 19:06

1 Answers1

9

Here is a method that will produce a rectangular array of numbers that are given as columns.

  • The syntax is stacksys{col 1;col 2;...;col n} where col i is the ith column with entries given as a comma separated list.
  • I fiddled with the input a bit to show how column and row headings can be introduced.
  • array can be changed to tabular if that is desired.
  • \stacksys{,r1,r2;c1,1,2;c2,3,4;c3,5,6} produces:

enter image description here

Code:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\int_new:N \l_ss_num_cols_int
\int_new:N \l_ss_num_rows_int
\bool_new:N \l_has_run_bool

\NewDocumentCommand{\stacksys} {m}
    {
        \ss_make_table:n {#1}
        \int_step_inline:nnnn {1}{1}{\l_ss_num_rows_int}
            {
                \tl_put_right:cn {l_row_{##1}_tl}{\\}
            }
        \begin{array}{*{\int_use:N \l_ss_num_cols_int}{c}}
        \int_step_inline:nnnn {1}{1}{\l_ss_num_rows_int}
            {
                \tl_use:c {l_row_{##1}_tl}
            }
        \end{array}
        \bool_gset_false:N \l_has_run_bool
    }

\cs_new_protected:Npn \ss_make_table:n #1
    {
        \seq_set_split:Nnn \l_tmpa_seq {;} {#1}
        \int_set:Nn \l_ss_num_cols_int {\seq_count:N \l_tmpa_seq}
        \seq_map_function:NN \l_tmpa_seq \ss_process_cols:n
    }

\cs_new_protected:Npn \ss_process_cols:n #1
    {
        \int_zero:N \l_tmpa_int
        \seq_set_split:Nnn \l_tmpb_seq {,} {#1}
        \int_set:Nn \l_ss_num_rows_int {\seq_count:N \l_tmpb_seq}
        \seq_map_inline:Nn \l_tmpb_seq
            {
                \bool_if:NTF \l_has_run_bool
                    {
                      \int_incr:N \l_tmpa_int
                      \tl_put_right:cn {l_row_{\int_use:N \l_tmpa_int}_tl} {&##1}
                    }
                    {
                      \int_incr:N \l_tmpa_int
                      \tl_clear_new:c {l_row_{\int_use:N \l_tmpa_int}_tl}
                      \tl_put_right:cn {l_row_{\int_use:N \l_tmpa_int}_tl} {##1}
                    }
            }
        \bool_gset_true:N \l_has_run_bool
    }

\ExplSyntaxOff
\begin{document}
\[
\stacksys{,r1,r2;c1,1,2;c2,3,4;c3,5,6}
\]
\end{document}
Scott H.
  • 11,047
  • Thanks! I'm adjusting it now to my specific situation (and taking care of 100 other tasks). Once I've got it working, I'll accept. Your use of :c is so simple and clear, thanks! [current project is detecting the last row, since somehow I'm getting an extra visible row when using {r|rrr} style tabular headings] – Jack Schmidt Sep 05 '12 at 18:45
  • Officially, the colon is supposed to work in \seq_set_split:Nnn right? I agree it doesn't, and I couldn't make it work. The hyphen looks fine, so this is only for curiosity. – Jack Schmidt Sep 05 '12 at 18:46
  • I'm not exactly sure, I thought that it "should" work (I couldn't find it excluded in the documentation), but was getting errors so I changed it to the "-". You can switch that to something else if you like. – Scott H. Sep 05 '12 at 18:49
  • @ScottH. A few 'LaTeX3 Best Practice' comments. I'd encourage you to pick a single prefix here, both for functions and variables (namespace management is important!): perhaps tabtran? Most of your stuff is not expandable, so \cs_new_protected:Npn would be better than \cs_new:Npn. In 'integer expression' contexts you don't need \int_use:N as TeX automatically does this, so we tend to leave them out. – Joseph Wright Sep 05 '12 at 19:10
  • @JosephWright Thanks for the suggestions. I've been trying to remember to add a unique prefix, but forgot this time! For the second I'll have to read up on protected as I don't really understand when it is required. I interpret the third as meaning that if an integer expression is expected, then the \int_use:N is unnecessary because \int_eval is applied anyway? – Scott H. Sep 05 '12 at 19:21
  • @ScottH. If your function uses any material from interface3 and there is no star in the docs, you should use protected. The most common case is that anything which performs an assignment (setting a variable) must be protected. AS you say, integer expressions all carry out the equivalent of \int_eval:n internally. – Joseph Wright Sep 05 '12 at 19:28
  • @ScottH. Thanks! If you want to only give the bottom answer, that is fine with me. I ended up changing the syntax a little (and using tl_head). I think your second answer will help more people. :-) – Jack Schmidt Sep 06 '12 at 16:21
  • Oh, but one thing I had to fix that might not be obvious: if you use the command twice, terrible things happen. You just need to zero out the global variables at the beginning, so an easy fix. – Jack Schmidt Sep 06 '12 at 16:22
  • Oops, my bad...forgot to clear things. If you're ok with the second answer I'll just get rid of the first as it's a bit more sloppy and a bit less general. Glad it works for you. – Scott H. Sep 06 '12 at 17:52
  • It's not worth an edit but \bool_gset_false on line 21 can be deleted if \bool_gset_true on line 49 is changed to \bool_set_true – Scott H. Sep 06 '12 at 20:06