13

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
   readvalue from list A
   readvalue from list B
   create new variable aname1{}
repeat

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

lockstep
  • 250,273
yannisl
  • 117,160
  • 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. – yannisl Feb 27 '11 at 14:23

7 Answers7

9

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
Ahmed Musa
  • 11,742
  • 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? – yannisl 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 –  Feb 27 '11 at 14:57
  • 2
    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 06 '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 07 '12 at 00:47
  • @Herbert: \psforeach can't do auto-completion \psforeach\A{a,...,g}? – Ahmed Musa Oct 07 '12 at 00:57
  • @AhmedMusa: that is only possible for numerical values –  Oct 08 '12 at 07:20
9

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}
  • 1
    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
  • 1
    @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
  • @Will: Mine is expandable, but quadratic. Is the non-expandable version also quadratic? --- It could be nicer to map_function on the zipped clist. For that we need either a map_function_unbraced, which leaves the argument unbraced, or a map_function:Nn which maps a bunch of tokens rater than just a function (I'd prefer that.) Then \clist_map_function:Nn \l_tmpa_clist {\exp_after:wN \foo:nn \use:n} will map a function of two variables on a zipped clist. --- Do you generate_variant ... {V} and then rename to N? – Bruno Le Floch Mar 01 '11 at 07:18
  • @bruno To be honest I haven't looked too much into the efficiency of the mapthread function; it works by popping items off the stack and passing the values to the mapping function; I suspect, but I'm not sure, it might be faster for large lists. For these faux-V functions, we just create them manually with \exp_args. – Will Robertson Mar 02 '11 at 11:18
4

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.

Martin Scharrer
  • 262,582
2

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}

% your list
\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
  \meaning\firstofA \qquad \meaning\restofA\qquad
  \meaning\firstofB \qquad \meaning\restofB\par}%
% 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}
David Carlisle
  • 757,742
Leo Liu
  • 77,365
2

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

1

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

\readarray{Names}{two&three&five&seven&eleven}
\readarray{Primes}{2&3&5&7&11&13}

\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}

enter image description here

David Carlisle
  • 757,742
Leo Liu
  • 77,365
1

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}

enter image description here

Ahmed Musa
  • 11,742