In functional languages, there often is a function called zip, which can be handy in many situations; for example in ruby, one can say:

%w(a b c).zip(%w(d e f))

to get an array of arrays [["a", "d"], ["b", "e"], ["c", "f"]].

It seems that there are many ways to make a zip function, either with a map (another one), or foldr. (The map might actually be done with foldr.)

But given that the lambda.sty lists do not seem to support nested lists, I'm not sure how would one go about making zip in TeX (with lambda.sty).

I tried something along the lines of which seemed the most simple;

\input lambda.sty




but it results in [abcd, abce, abcf]. So, close but no cigar.

Would you have an idea?

I wouldn't actually need there to be nested lists as a result, just that for lista zipped with listb, some macro \foo#1#2 would get run for (continuing with the example lists) three times, getting as its arguments on the first run #1<-a, #2<-d, on the second run #1<-b, #2<-e, and on the third #1<-c, #2<-f.

  • 1
    Are the lists always the same length?
    – Werner
    Sep 13 '12 at 16:19
  • @Werner: the lists can be any length, but the two lists need to always be of same length.
    – morbusg
    Sep 13 '12 at 16:20

Great to see expl3 in use in egreg's answer. Given the recent developments in the LaTeX3 interfaces I would like to "slightly" improve or change it as follows:


% package level definitions

\seq_new:N \l__morbusg_left_seq
\seq_new:N \l__morbusg_right_seq

% public package interface command

\cs_new:Npn \morbusg_zip:Nnnnn #1 #2 #3 #4 #5 {
  \seq_set_split:Nnn \l__morbusg_left_seq   { #2 } { #3 }
  \seq_set_split:Nnn \l__morbusg_right_seq { #4 } { #5 }
  \seq_mapthread_function:NNN \l__morbusg_left_seq \l__morbusg_right_seq #1

% xparse interface

\NewDocumentCommand{\zip}{ m O{,} m o m }
 { \IfNoValueTF {#4}
      { \morbusg_zip:Nnnnn #1 {#2} {#3} {#2} {#5} }
      { \morbusg_zip:Nnnnn #1 {#2} {#3} {#4} {#5} }


\newcommand{\foo}[2]{First is #1, second is #2\par}
\newcommand{\baz}[2]{Left is #1, right is #2\par}





So what is different?

Clear separation of functional interface \morbusg_zip:Nnnnn and document level interface \zip.

As I outlined in What can *I* do to help the LaTeX3 Project the architecture of LaTeX3 separates out the two very clearly. xparseis a document level syntax package that implements LaTeX2e like syntax with optional arguments etc. But there might, and probably will, be alternatives, e.g., a key/value type of document level syntax, or an xml like syntax.

If properly separated the functional code, i.e., \morbusg_zip:Nnnnnwould remain unchanged and you only switch the document level parsing routines. Maybe feels a bit like overkill in this particular example, but it helps to think in this way throughout and use the separation as best practice.

Support for different separators on the two lists.

The original solution used one and the same operator. I extended the functional definition to use one separator per list. So the functional definition offers more possibilities and it is up to the user interface to use or not use them, e.g., to get egreg's interface the definition for \zip would have been

\NewDocumentCommand{\zip}{ m O{,} m m }
    { \morbusg_zip:Nnnnn #1 {#2} {#3} {#2} {#4} }

i.e., reusing the same separator for both lists. Again that shows (imnsho) the advantage of decoupling the two levels.

Clearly separating public interface and internal commands and variables

Well, this time we only have one function and it is public, so only the two variables got marked as internal (which is they use __ in their name). In a package one would/could have shortened that to \l_@@_left_seq where @@ is a shorthand for the current "module" name with two __in front.

If the extended version is run through TeX you will get the following output (as you can see, stranger separators are possible too, e.g., !!, or $ but that was already in the original solution):

enter image description here

  • 4
    Very nice lecture!
    – egreg
    Sep 13 '12 at 22:38
  • Maybe we should rename the 'mapthread' function to \seq_zip:NNN or something similar? (Perhaps \seq_map_zip:NNN, as we're trying to show what is a mapping.)
    – Joseph Wright
    Sep 14 '12 at 7:17

Something like this?

\seq_new:N \l_morbusg_left_seq
\seq_new:N \l_morbusg_right_seq

\NewDocumentCommand{\zip}{ m O{,} m m }
  \seq_set_split:Nnn \l_morbusg_left_seq  { #2 } { #3 }
  \seq_set_split:Nnn \l_morbusg_right_seq { #2 } { #4 }
  \seq_mapthread_function:NNN \l_morbusg_left_seq \l_morbusg_right_seq #1

\newcommand{\foo}[2]{First is #1, second is #2\par}

\newcommand{\baz}[2]{Left is #1, right is #2\par}



The first argument to \zip is the two argument macro to apply; the optional argument is the list separator; then the two lists.

enter image description here

  • had some "stylistic" comments due to latest thinking in the LaTeX3 team, but that didn't fit a comment, so made it a separate answer. Sep 13 '12 at 22:13
  • egreg, @Frank: Thanks for an expl3 answer, I'm sure it's very useful for its users. :-)
    – morbusg
    Sep 14 '12 at 3:43

The LaTeX3 solutions are probably how I'd tackle this (the sequence data structure is very well-designed), but if we assume that we can assume the lists use a comma separator as shown in the question then something like

\catcode`\@=11 %
\catcode`\@=12 %
\csname @@end\endcsname

will work. (Note: this requires pdfTeX, but can also be worked with XeTeX and LuaTeX with the correct set up for \pdfstrcmp. Setting up without this primitive is doable but more awkward.)

The idea here is to grab one item from each list at a time. Each one is checked for the end marker \q@recursion@tail: I've assumed that the mapping simply stops when the shorter list ends.

  • It would of course be possible to set up \zip to generate the required auxiliary 'on the fly' with a variable list separator token.
    – Joseph Wright
    Sep 14 '12 at 7:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.