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.

2
  • Did you intend to have two lists with different lengths? I.e., do you want 35 total pairs aname1, aname2,...,aname5,...,gname1,...,gname5 ? Commented Feb 27, 2011 at 12:55
  • @will lists should be of the same length, but I want to capture any potential errors.
    – yannisl
    Commented Feb 27, 2011 at 14:23

7 Answers 7

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
6
  • 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
    Commented Feb 27, 2011 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
    – user2478
    Commented Feb 27, 2011 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. Commented Oct 6, 2012 at 20:14
  • @MMMM: Yiannis Lazarides seems to be happy if this requirement isn't met. I too thought he needed it.
    – Ahmed Musa
    Commented Oct 7, 2012 at 0:47
  • @Herbert: \psforeach can't do auto-completion \psforeach\A{a,...,g}?
    – Ahmed Musa
    Commented Oct 7, 2012 at 0:57
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}
7
  • 1
    zip with TeX. Now I've seen everything. :‑)
    – morbusg
    Commented Feb 27, 2011 at 14:13
  • @morbusg. I'm still trying to figure out an efficient way of zipping n lists rather than 2. Commented Feb 27, 2011 at 14:25
  • It is possible to implement a set of list operations like LISP language?
    – Leo Liu
    Commented Feb 27, 2011 at 14:30
  • 1
    @Leo Liu. I don't know Lisp, but if you describe what you would like, I can code it. Commented Feb 27, 2011 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. Commented Feb 28, 2011 at 21:29
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.

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

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

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

You must log in to answer this question.

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