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 :'(

3 Answers 3

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.

9
  • 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
    Commented Jul 21, 2020 at 7: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
    Commented Jul 21, 2020 at 16:45
  • @iago-lito'consideringleaving Did you say that you want to pass lists buried in macros?
    – egreg
    Commented Jul 21, 2020 at 17:15
  • @iago-lito'consideringleaving I added the modified code.
    – egreg
    Commented Jul 21, 2020 at 17:23
  • 1
    @iago-lito'consideringleaving Of course it doesn't: o just does one expansion. I cannot read your mind.
    – egreg
    Commented Jul 22, 2020 at 8:38
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

2
  • 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
    Commented Jul 21, 2020 at 7:07
  • @iago-lito'consideringleaving It is up to you to select the best approach for your situation. Thanks for the consideration. Commented Jul 21, 2020 at 9: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}
2
  • Is this a pure TeX solution? :D I'm impressed. How hard do you think it would be to achieve the full behaviour like this?
    – iago-lito
    Commented Jul 21, 2020 at 7:08
  • 1
    @iago-lito'consideringleaving not hard but not at midnight:-) Commented Jul 21, 2020 at 7:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .