9

I am trying without success to combine these two approaches into something powerful that would allow me to zip-iterate over two lists and do arbitrary cool things:

\zip{a,b,c}{1,2,3}{#1-#2 }

would expand to

a-1 b-2 c-3 

And why not

\zip[#1/#2][#3::#4]
    {a/A,b/B,c/C}
    {1::I,2::II,3::III}
    {Grand #1 is #2 but grand #3 is #4.\\}

would expand to

Grand a is A but grand 1 is I.\\
Grand b is B but grand 2 is II.\\
Grand c is C but grand 3 is III.\\

My best attempt so far was to append this code after @BrunoLeFloch's solution:

\NewDocumentCommand{\zip}{mmm}
 {
  \cs_set:Npn \__egreg_apply_aux:nn ##1 \q_stop { #3 }
  \clist_map_zip_ii:VVN #1 #2 \__egreg_apply_aux:nn
 }

with no luck, and maybe the arcanes of Interface3 find me not pure-hearted enough to unveil their secrets yet :'(

iago-lito
  • 1,472

3 Answers3

7

EDIT to provide full answer to all scenarios posed by OP. As long as there are always two lists to combine, the effort can be done with listofitems.

Optional sub-argument separators can be multi-token; however they cannot include / or |, though if this is an issue, let me know. Perhaps some accommodation can be had.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{listofitems}
\def\zipaux{}
\newcommand\zip[1][]{%
  \def\tmpA{#1}%
  \zipB%
}
\newcommand\zipB[1][]{%
  \expandafter\zipC\expandafter{\tmpA}{#1}%
}
\newcommand\zipC[5]{%
  \ifx\relax#1\relax
    \setsepchar{,}%
    \readlist*\argA{#3}%
    \ifx\relax#2\relax
      \renewcommand\zipaux[2]{#5}%
      \setsepchar{,}
    \else
      \renewcommand\zipaux[3]{#5}%
      \setsepchar{,/#2}
    \fi
  \else
    \setsepchar{,/#1}%
    \readlist*\argA{#3}%
    \ifx\relax#2\relax
      \renewcommand\zipaux[3]{#5}%
      \setsepchar{,}
    \else
      \renewcommand\zipaux[4]{#5}%
      \setsepchar{,/#2}
    \fi
  \fi%
  \readlist*\argB{#4}%
  \foreachitem\z\in\argB{%
    \ifx\relax#1\relax
      \ifx\relax#2\relax
        \zipaux{\argA[\zcnt]}{\argB[\zcnt]}%
      \else
        \zipaux{\argA[\zcnt]}{\argB[\zcnt,1]}{\argB[\zcnt,2]}%
      \fi
    \else
      \ifx\relax#2\relax
        \zipaux{\argA[\zcnt,1]}{\argA[\zcnt,2]}{\argB[\zcnt]}%
      \else
        \zipaux{\argA[\zcnt,1]}{\argA[\zcnt,2]}
               {\argB[\zcnt,1]}{\argB[\zcnt,2]}%
      \fi
    \fi
  }%
}
\begin{document}
\zip{a,b,c}{1,2,3}{#1-#2 }

\zip[?]{a?A,b?B,c?C}{1,2,3}{(#1*#2)-#3 }

\zip[?][::]{a?A,b?B,c?C}{1::X,2::Y,3::Z}{(#1*#2)-#3/#4 }

\zip[][::]{a,b,c}{1::X,2::Y,3::Z}{(#1-#2)/#3 } \end{document}

enter image description here

  • Thank you! I'm impressed this can be achieved without even using interface3! If you don't mind, I'll still accept egreg's answer because the optional arguments are even more flexible there :) – iago-lito Jul 21 '20 at 07:07
  • @iago-lito'consideringleaving It is up to you to select the best approach for your situation. Thanks for the consideration. – Steven B. Segletes Jul 21 '20 at 09:09
6

The simple case with two arguments

enter image description here

\documentclass{article}

\begin{document}

\def\zip#1#2#3{% \def\z##1##2{#3}% \xzip#1,\relax#2,\relax} \def\xzip#1,#2\relax#3,#4\relax{% \z{#1}{#3}% \if\relax\detokenize{#2}\relax \expandafter\zzgobble \fi \xzip#2\relax#4\relax }

\def\zzgobble#1\relax#2\relax{}

\zip{a,b,c}{1,2,3}{#1-#2 } \end{document}

David Carlisle
  • 757,742
6

Defining \zip in the first way is quite easy:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\zip}{mm +m} { \seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #1 } \seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #2 } \cs_set:Nn __iagolito_zip:nn { #3 } \seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq __iagolito_zip:nn }

\seq_new:N \l__iagolito_zip_a_seq \seq_new:N \l__iagolito_zip_b_seq

\ExplSyntaxOff

\begin{document}

\zip{a,b,c}{1,2,3}{#1-#2 }

\end{document}

You can check this prints

a-1 b-2 c-3

If the two lists have different number of elements, the loop ends when either list ends.


The more complex features can be accomplished as well. Beware that the two optional argument must both appear, if the complex processing is needed.

The idea is to populate another sequence where the two lists are merged and then an auxiliary macro can be applied.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\zip}{oomm +m} { \IfNoValueTF { #1 } { \iagolito_zip_simple:nnn { #3 } { #4 } { #5 } } { \iagolito_zip_full:nnnnn { #1 } { #2 } { #3 } { #4 } { #5 } } }

\seq_new:N \l__iagolito_zip_a_seq \seq_new:N \l__iagolito_zip_b_seq \seq_new:N \l__iagolito_zip_c_seq

\cs_new_protected:Nn \iagolito_zip_simple:nnn { \seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #1 } \seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #2 } \cs_set:Nn __iagolito_zip:nn { #3 } \seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq __iagolito_zip:nn }

\cs_new_protected:Nn \iagolito_zip_full:nnnnn { \seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #3 } \seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #4 } \seq_clear:N \l__iagolito_zip_c_seq \cs_set:Npn __iagolito_zip_process:w #1 \q_stop #2 \q_stop { #5 } \seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq __iagolito_merge:nn \seq_map_inline:Nn \l__iagolito_zip_c_seq { ##1 } }

\cs_new_protected:Nn __iagolito_merge:nn { \seq_put_right:Nn \l__iagolito_zip_c_seq { __iagolito_zip_process:w #1 \q_stop #2 \q_stop } }

\ExplSyntaxOff

\begin{document}

\zip{a,b,c}{1,2,3}{#1-#2 }

\zip[#1/#2][#3::#4] {a/A,b/B,c/C} {1::I,2::II,3::III} {Grand #1 is #2 but grand #3 is #4.\par}

\end{document}

enter image description here

If you want to pass macros expanding to lists, do one step expansion.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\zip}{oomm +m} { \IfNoValueTF { #1 } { \iagolito_zip_simple:oon { #3 } { #4 } { #5 } } { \iagolito_zip_full:nnoon { #1 } { #2 } { #3 } { #4 } { #5 } } }

\seq_new:N \l__iagolito_zip_a_seq \seq_new:N \l__iagolito_zip_b_seq \seq_new:N \l__iagolito_zip_c_seq

\cs_new_protected:Nn \iagolito_zip_simple:nnn { \seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #1 } \seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #2 } \cs_set:Nn __iagolito_zip:nn { #3 } \seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq __iagolito_zip:nn } \cs_generate_variant:Nn \iagolito_zip_simple:nnn { oo }

\cs_new_protected:Nn \iagolito_zip_full:nnnnn { \seq_set_from_clist:Nn \l__iagolito_zip_a_seq { #3 } \seq_set_from_clist:Nn \l__iagolito_zip_b_seq { #4 } \seq_clear:N \l__iagolito_zip_c_seq \cs_set:Npn __iagolito_zip_process:w #1 \q_stop #2 \q_stop { #5 } \seq_mapthread_function:NNN \l__iagolito_zip_a_seq \l__iagolito_zip_b_seq __iagolito_merge:nn \seq_map_inline:Nn \l__iagolito_zip_c_seq { ##1 } } \cs_generate_variant:Nn \iagolito_zip_full:nnnnn { nnoo }

\cs_new_protected:Nn __iagolito_merge:nn { \seq_put_right:Nn \l__iagolito_zip_c_seq { __iagolito_zip_process:w #1 \q_stop #2 \q_stop } }

\ExplSyntaxOff

\newcommand{\listA}{a,b,c} \newcommand{\listB}{1,2,3} \newcommand{\listC}{a/A,b/B,c/C} \newcommand{\listD}{1::I,2::II,3::III}

\begin{document}

\zip{a,b,c}{1,2,3}{#1-#2 } \zip{\listA}{1,2,3}{#1-#2 } \zip{a,b,c}{\listB}{#1-#2 } \zip{\listA}{\listB}{#1-#2 }

\zip[#1/#2][#3::#4] {a/A,b/B,c/C} {1::I,2::II,3::III} {Grand #1 is #2 but grand #3 is #4.\par}

\zip[#1/#2][#3::#4] {\listC} {1::I,2::II,3::III} {Grand #1 is #2 but grand #3 is #4.\par}

\zip[#1/#2][#3::#4] {a/A,b/B,c/C} {\listD} {Grand #1 is #2 but grand #3 is #4.\par}

\zip[#1/#2][#3::#4] {\listC} {\listD} {Grand #1 is #2 but grand #3 is #4.\par}

\end{document}

The output is the same as before, just repeated four times for each instance.

egreg
  • 1,121,712
  • Well, you nailed it (again), thank you :D I love the way you can parse flexible input forms this way, like with \zip[#1: #2 (#3)][#4/#5 -- #6 and #7]{..}{..}. I think this is an awesome path towards syntax extension in LaTeX. It makes complicated things comfortable to read and write :') – iago-lito Jul 21 '20 at 07:12
  • I still have severe expansion troubles when the lists results from another command. \zip{\mylist}{a,b,c} doesn't work as expected: \mylist is always considered singleton. I can fix this with \expandafter\zip\expandafter{\mylist}{a,b,c}, but this hack doesn't extend to other situations: \zip{a,b,c}{\mylist}, \zip{\mylist}{\myotherlist}, \zip[#1-#2][#3]{\mylist}{a,b,c},etc. Is there a consistent way to solve the expansion problems? Or how do I count the number of \expandafter to insert in each situation? Can I control the expansion level with \zip[#1][#2][0][1]{a,b,c}{\mylist}? – iago-lito Jul 21 '20 at 16:45
  • @iago-lito'consideringleaving Did you say that you want to pass lists buried in macros? – egreg Jul 21 '20 at 17:15
  • @iago-lito'consideringleaving I added the modified code. – egreg Jul 21 '20 at 17:23
  • I did not. I mean not in the OP, but it turns out that it is my principal use case, because otherwise I could always have rewritten \zip{a,b,c}{1,2,3} as \apply[#1-#2]{a-1,b-2,c-3} with your previous solution, which I cannot do with \zip{a,b,c}{\aBurriedList} :\ Do you think this buried-in-macro problem deserves another post? – iago-lito Jul 21 '20 at 17:24
  • @iago-lito'consideringleaving No, the change was straightforward – egreg Jul 21 '20 at 17:24
  • Thank you. The new version does not work if my list happens to be burried within 2 macros levels like \def\firstLevel{a,b,c}\def\secondLevel{\firstLevel}, but it works if I change the new oo argument types to ff instead. Not sure what the limitation is now, but I haven't hit it yet so I'm happy with it :) – iago-lito Jul 22 '20 at 08:23
  • 1
    @iago-lito'consideringleaving Of course it doesn't: o just does one expansion. I cannot read your mind. – egreg Jul 22 '20 at 08:38
  • Arf, sometimes I cannot either X) – iago-lito Jul 22 '20 at 08:43