# Iterating through two lists

I have two comma delimited lists:

ListA(a,b,c,d,e,f,g)


and

ListB(name1,name2,name3,name4,name5)


I would like to iterate through both lists at the same time, something like:

loop
create new variable aname1{}
repeat


Iteration should stop, if it reaches to the end of either lists.

-
Did you intend to have two lists with different lengths? I.e., do you want 35 total pairs aname1, aname2,...,aname5,...,gname1,...,gname5 ? – Will Robertson Feb 27 '11 at 12:55
@will lists should be of the same length, but I want to capture any potential errors. – Yiannis Lazarides Feb 27 '11 at 14:23

A solution which goes through all elements:

\documentclass{article}
\usepackage{pstricks}
\psforeach{\A}{a,b,c,d,e,f,g}{%
\psforeach{\B}{name1,name2,name3,name4,name5}{%
\expandafter\xdef\csname\A\B\endcsname{Def: \A,\B}}}

\begin{document}
\csname aname1\endcsname

\csname ename4\endcsname
\end{document}


Make sure comma isn't active, otherwise this won't work. For example, the following fails. Also, make sure there are no spurious spaces in your lists.

\begingroup
\catcode\,=13
\def\x{\endgroup
\psforeach{\A}{a,b,c,d,e,f,g}{%
\psforeach{\B}{name1,name2,name3,name4,name5}{%
\expandafter\xdef\csname\A\B\endcsname{Def: \A,\B}%
}%
}%
}
\x

-
Nice and very clean solution. Is there a way to get the length of the lists or break out of the loops, based on a conditional? – Yiannis Lazarides Feb 27 '11 at 14:40
@saltypen: with package xstring you can check a given list: \def\ListA{a,b,c,d,e,f,g} \StrCount{\ListA,}{,}[\Number] The macro \Number returns 7 – Herbert Feb 27 '11 at 14:57
Please note, that this solution will create all combinations (two-dimensional set), whereas the question implies that only the pairs with the same index should be created (one-dimensional set). This might be a problem in some cases, as the variable aname2 would be unintentionally overwritten by the current solution, if it existed before. – Patrick Häcker Oct 6 '12 at 20:14
@MMMM: Yiannis Lazarides seems to be happy if this requirement isn't met. I too thought he needed it. – Ahmed Musa Oct 7 '12 at 0:47
@Herbert: \psforeach can't do auto-completion \psforeach\A{a,...,g}? – Ahmed Musa Oct 7 '12 at 0:57

You can map a function on two comma-separated list, using the code below. It also lets you "zip" two comma separated lists together. All this is expandable, e.g. suitable for use in a \write statement, etc. Or rather, it is expandable if the function you map is itself expandable. See the end of the code for an example suited to your case.

\documentclass{minimal}
\usepackage{expl3}

\ExplSyntaxOn
% Spaces are now ignored, and _ and : can be used in macro names.
%
% \tl_if_either_empty_ii:nn tests whether either one of two token
% lists is empty.
%
\prg_new_conditional:Npnn \tl_if_either_empty_ii:nn #1 #2 {p,T,F,TF} {
\tl_if_empty:nTF {#1} {\prg_return_true:} {
\tl_if_empty:nTF {#2} {\prg_return_true:} {\prg_return_false:}
}
}

% Function to zip two clist together, e.g.,
%   {1,2,3,4,5} {aa,bb,cc,d} -> {1}{aa}, {2}{bb}, {3}{cc}, {4}{d}
% It stops when reaching the end of any of the two lists. For people who
% care: it is f-expandable.
%
\cs_new:Npn \clist_zip_ii:nn #1 #2 {
\clist_zip_ii_aux:nw {} #1, \q_mark, #2, \q_mark.
}
\cs_new:Npn \clist_zip_ii_aux:nw #1 #2, #3 \q_mark, #4, #5 \q_mark. {
\tl_if_either_empty_ii:nnTF {#3} {#5} {
#1 {#2}{#4}
}{
\clist_zip_ii_aux:nw {#1 {#2}{#4},} #3 \q_mark, #5 \q_mark.
}
}
\cs_generate_variant:Nn \clist_zip_ii:nn {VV}

% To map a function #3 of two arguments onto the zipped result,
% we do something similar, essentially replacing commas by #1 in
% the output.
\cs_new:Npn \clist_map_zip_ii:nnN #1 #2 #3 {
\clist_map_zip_ii_aux:Nnw #3 {} #1, \q_mark, #2, \q_mark.
}
\cs_new:Npn \clist_map_zip_ii_aux:Nnw #1 #2 #3, #4 \q_mark, #5, #6 \q_mark. {
\tl_if_either_empty_ii:nnTF {#4} {#6} {
#2 #1{#3}{#5}
}{
\clist_map_zip_ii_aux:Nnw #1 {#2 #1{#3}{#5}} #4 \q_mark, #6 \q_mark.
}
}
\cs_generate_variant:Nn \clist_map_zip_ii:nnN {VV}

% ======================= Your comma separated lists ==================
% All those g mean global.
\clist_new:N \g_my_first_clist
\clist_new:N \g_my_second_clist
\clist_gput_right:Nn \g_my_first_clist {a,b,c,d,e,f}
\clist_gput_right:Nn \g_my_second_clist {1,2,3,4,5}

\cs_new:Npn \my_create_variable:nn #1 #2 {
\iow_term:n {Creating~variable~#1 name #2''} % Message to the terminal
\tl_new:c {#1 name #2}
}

\clist_map_zip_ii:VVN \g_my_first_clist
\g_my_second_clist
\my_create_variable:nn

% Restore the usual behaviour of space, colon and underscore.
\ExplSyntaxOff

\begin{document}
\end{document}

-
zip with TeX. Now I've seen everything. :‑) – morbusg Feb 27 '11 at 14:13
@morbusg. I'm still trying to figure out an efficient way of zipping n lists rather than 2. – Bruno Le Floch Feb 27 '11 at 14:25
It is possible to implement a set of list operations like LISP language? – Leo Liu Feb 27 '11 at 14:30
@Leo Liu. I don't know Lisp, but if you describe what you would like, I can code it. – Bruno Le Floch Feb 27 '11 at 20:09
Nice! In l3candidates I've also got a mapthread function for seqs using a non-expandable stack-based approach. Yours is better, I think, for most applications. P.S. Although it's kinda idiosyncratic, we use the "N" argument to denote a clist variable. Perhaps this was a bad decision, but it's consistent with seq. – Will Robertson Feb 28 '11 at 21:29

If you don't mind global assignments and to interleave the lists you can use the \foreach macro of pgffor:

\documentclass{article}
\usepackage{pgffor}
\begin{document}
\foreach \name/\value in {namea/a,nameb/b,namec/c} {%
\global\expandafter\def\csname\name\expandafter\endcsname\expandafter{\value}%
}
% Test:
\show\namea \show\nameb \show\namec
\end{document}


Otherwise you need to program you own loop which removes a value from each list. Just look e.g. how the \@for loop is defined in latex.ltx.

-

Here is another solution using simple iteration. I'm sorry that the code is not very clean.

\documentclass{article}
\makeatletter

\long\def\getfirst@#1,#2\@@#3{\def#3{#1}}
\long\def\getfirst#1#2{%
\def\temp{#2}%
\expandafter\getfirst@\temp,\@nil\@@#1}

\def\@nil@{\@nil}
\long\def\getrest@#1,#2\@@#3{\def\temp{#2}%
\ifx\@nil@\temp
\let#3\undefined
\else
\expandafter\getrest@@\temp#3
\fi}
\long\def\getrest@@#1,\@nil#2{\def#2{#1}}
\long\def\getrest#1#2{%
\def\temp{#2}%
\expandafter\getrest@\temp,\@nil\@@#1}

\long\def\split#1#2#3{%
\def\temp{#3}%
\expandafter\getfirst@\temp,\@nil\@@#1
\def\temp{#3}%
\expandafter\getrest@\temp,\@nil\@@#2}

\begin{document}

\def\listA{a,b,c,d,e,f}
\def\listB{X,Y,Z,W}

\newif\ifloop
\def\testloop{%
\ifx\listA\undefined \loopfalse \fi
\ifx\listB\undefined \loopfalse \fi
\ifloop}
\looptrue
\loop
% extract list
\expandafter\split\expandafter\firstofA\expandafter\restofA\expandafter{\listA}
\expandafter\split\expandafter\firstofB\expandafter\restofB\expandafter{\listB}
% show progress
{\tt
% do definition
\expandafter\edef \csname TT\firstofA\endcsname {\firstofB}%
% iteration
\let\listA\restofA
\let\listB\restofB
\testloop\repeat

result:\\
\verb=\TTa= is \TTa\\
\verb=\TTb= is \TTb\\
\verb=\TTc= is \TTc\\
\verb=\TTd= is \TTd

\end{document}

-

Even though the question is a bit older, the general problem still occurs from time to time. The following generic solution defines the macro \forlistlooptwo, which works as etoolbox' \forlistloop. There are two extensions: First, it expects two comma separated lists instead of one and second, the handler function expects two arguments instead of one.

Use lists of equal lengths and avoid spaces and semicolons in the lists and in the elements, respectively, as I wanted to keep the code short.

\documentclass{minimal}
\usepackage{xstring}
\usepackage{etoolbox}

\def\forlistlooptwo#1#2#3{%
\ifboolexpr{test{\IfSubStr{#2}{,}} and test{\IfSubStr{#3}{,}}}{%
% Evaluate first pair and call back with reduced lists if there are at least two elements in each list..
\forlistlooptwohelper{#1}#2;#3;%
}{%
\ifboolexpr{test{\notblank{#2}} and test{\notblank{#3}}}{%
% Evaluate last pair, if there is one element in each list.
#1{#2}{#3}%
}{}%
}%
}
\def\forlistlooptwohelper#1#2,#3;#4,#5;{%
% Call the handler with the first pair as the two arguments.
#1{#2}{#4}%
% Call the loop macro again with the lists reduced by the first pair.
\forlistlooptwo{#1}{#3}{#5}%
}

\begin{document}
\def\createVariableFromPair#1#2{%
\csdef{#1#2}{}%
}%
\forlistlooptwo{\createVariableFromPair}{a,b,c}{name1,name2,name3}%
\ifcsdef{bname2}{true}{false}, \ifcsdef{bname3}{true}{false}
\end{document}


The example outputs as demanded: true, false

-

For convenience, arrayjobx package can do the trick, although the data are not separated by commas.

\documentclass{article}
\usepackage{arrayjobx}
\usepackage{ifthen}
\newcounter{ind}

\begin{document}
\newarray\Names
\newarray\Primes

\noindent
\setcounter{ind}{1}%
\whiledo{\value{ind}<10}{%
% show progress
\theind: \Names(\theind)---\Primes(\theind)\\
% do definitions
\checkNames(\theind)\let\temp\cachedata
\checkPrimes(\theind)%
\expandafter\xdef\csname\temp\endcsname{\cachedata}%
% step index counter
\stepcounter{ind}}

\two, \three, \five, \seven, \eleven

\end{document}


-

Here is a solution that gives exit conditions as the OP indicated in the question. Also, active commas are welcome and spurious spaces in the list are trimmed.

\documentclass{article}
\usepackage{loops}

\def\ftoks{f}

\newforeach \x [
item counter = \xc, exit when = \ifx\x\ftoks\fi
] in {a,...,g} do {%
\let\xcc\xc
\newforeach [
count in = \yc all \y satisfying \ifnum\y>10\fi,
loop stopper = \ifnum\y>20\fi
] \y in {1,...,30} {%
\skvcsdef{#1##1}{Items: #1, ##1}%
}%
\let\xc\xcc
}

\begin{document}
Numbers: {\tt\string\xc}: \xc, {\tt\string\yc}: \yc

\skvcsuse{a1}

\skvcsuse{e10}

\end{document}
`

-