22

I'm defining a list and I need to obtain the n-th element of this list. I was surprised that etoolbox doesn't provide a way to obtain it. The way I found to obtain the n-th element is along these lines:

\documentclass{minimal}

\usepackage{etoolbox}

\newcounter{mylistcounter}

\def\mylist{}
\forcsvlist{\listadd\mylist}{%
   first element,
   second element,
   third element,
   fourth element,
   fifth element
}%

\def\getnthelement#1{%
   \setcounter{mylistcounter}{1}%
   \renewcommand*\do[1]{%
      \ifnumequal{\value{mylistcounter}}{#1}{##1\listbreak}\relax
      \stepcounter{mylistcounter}%
   }%
   \dolistloop{\mylist}
}%

\begin{document}
\def\fourthelement{\getnthelement{4}} % <--- Fine!
% \edef\fourthelement{\getnthelement{4}} % <--- Problem!

\begin{itemize}
\item The third element is: ``\getnthelement{3}''.
\item The fourth element is: ``\fourthelement''.
\item The fourth element again: ``\fourthelement''.
\item The fifth element is: ``\getnthelement{5}''.
\end{itemize}
\end{document}

Unfortunately, I need the same element several times, and I don't want to loop through the list each time, so I need to somewhat save it somewhere. I thought this would be achieved by using an \edef, but as you may see in the MWE above, with the \edef uncommented, I obtain the error:

! Undefined control sequence.
\GenericError  ...
                                                    #4  \errhelp \@err@     ...
l.27 \edef\fourthelement{\getnthelement{4}
                                              } % <--- Problem!
? h
The control sequence at the end of the top line
of your error message was never \def'ed. If you have
misspelled it (e.g., `\hobx'), type `I' and the correct
spelling (e.g., `I\hbox'). Otherwise just continue,
and I'll forget about whatever was undefined.

?

Any idea on how I could solve this problem? Any solution welcome, even solutions not using etoolbox. But please avoid non-stable packages. Also, I need a somewhat general method, since I need to use all the elements of the list several times, but always in the same shot.

4

5 Answers 5

21

If you need repeated access to arbitrary items then an "array" of command names \mylist1, \mylist2\ ... might be more suitable than a list.

\documentclass{minimal}

\usepackage{etoolbox}

\newcounter{mylistcounter}

\def\saveitem#1{%
\stepcounter{mylistcounter}%
\expandafter\def\csname mylist\themylistcounter\endcsname{#1}}

\forcsvlist{\saveitem}{%
   first element,
   second element,
   third element,
   fourth element,
   fifth element
}%


\def\getnthelement#1{\csname mylist#1\endcsname}


\begin{document}


\begin{itemize}
\item The third element is: ``\getnthelement{3}''.
\item The fourth element is: ``\getnthelement{4}''.
\item The fourth element again: ``\getnthelement{4}''.
\item The fifth element is: ``\getnthelement{5}''.
\end{itemize}
\end{document}
4
  • Accepting this answer. It works flawlessly and my colleagues will be happy colleagues! Nov 2, 2012 at 19:18
  • Remark, for large array there might be performance implication because of hash collision → macros - How to implement (low-level) arrays in TeX - TeX - LaTeX Stack Exchange
    – user202729
    Aug 23, 2022 at 14:25
  • @user202729 yes the csame hash in tex isn't state of the art compared to other possible hash that could have been used in the engine code, but in practice it is likely faster than any lookup coded within the tex macro layer, even if all names collide and so it degenerates to a linear search, a linear search in pascal/C is likely faster than anything coded in TeX for any list length likely to appear in a document Aug 23, 2022 at 14:34
  • I mean yes obviously unless you use toks register you clearly need at least O(n/s) hash entries to get O(s+log n) lookup performance, just saying the user may want to implement the tricks there to avoid some hash collision for extra performance (actually just notice this question is bumped, but anyway for future user reference)
    – user202729
    Aug 23, 2022 at 15:31
11

Here's a possible solution with xparse:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\newgniourflist}{ m }
 {
  \seq_new:c { g_gniourf_#1_seq }
 }
\newgniourflist{gniourflist}
\NewDocumentCommand{\addtogniourflist}{ O{gniourflist} m }
 {
  \seq_gput_right:cn { g_gniourf_#1_seq } { #2 }
 }
\NewExpandableDocumentCommand{\getnthelement}{ O{gniourflist} m }
 {
  \seq_item:cn { g_gniourf_#1_seq } { #2 }
 }
\NewDocumentCommand{\storenthelement}{ O{gniourflist} m m }
 {
  \cs_set:Npx #3 { \seq_item:cn { g_gniourf_#1_seq } { #2 } }
 }
\NewDocumentCommand{\cleargniourflist}{ O{gniourflist} }
 {
  \seq_gclear:c { g_gniourf_#1_seq }
 }
\ExplSyntaxOff

\newcommand{\fourthelement}{\getnthelement{4}}

\begin{document}

\addtogniourflist{first element}
\addtogniourflist{second element}
\addtogniourflist{third element}
\addtogniourflist{fourth element}
\addtogniourflist{fifth element}

\begin{itemize}
\item The third element is: ``\getnthelement{3}''.
\item The fourth element is: ``\fourthelement''.
\item The fourth element again: ``\fourthelement''.
\item The fifth element is: ``\getnthelement{5}''.
\end{itemize}

\storenthelement{4}{\playaroundelement}

\texttt{\meaning\playaroundelement}

\end{document}

The last lines show that the macro \playaroundelement is defined to expand just to fourth element.

With these macros you can manage more than one list; the default one is called gniourflist; if you want to clear it, you simply issue \cleargniourflist. But you can say

\newgniourflist{anotherlist}

and use the newlist as before, just adding an optional argument:

\addtogniourflist[anotherlist]{something}
\getnthelement[anotherlist]{1}
\storenthelement[anotherlist]{1}{\someelement}
\cleargniourflist[anotherlist]
9
  • Wow, LaTeX3 stuff... I'm having errors when compiling: ! Undefined control sequence. \\getnthelement #1#2->\seq_item:cn Nov 2, 2012 at 18:46
  • @gniourf_gniourf I'm not; update your TeX distribution: the key function \seq_item:cn is a recent addition.
    – egreg
    Nov 2, 2012 at 18:47
  • Oh, okay... I'm writing a small package to be shared with colleagues (to type exercise sheets), and I can't afford asking them to update their TeX distribution (most of them work on Windows XP and are already not very fond of LaTeX... they'll once again whine and say that MS Words is better). Nov 2, 2012 at 18:51
  • @gniourf_gniourf Sorry. But surely they are happily updating their word processing application, paying what is due, because otherwise they can't send documents to each other any more when one of them does the update. :(
    – egreg
    Nov 2, 2012 at 18:55
  • Yes, you are right, they are! Everything you say here is, unfortunately, absolutely true! Nov 2, 2012 at 18:58
4

The following solution too is fast and defines one macro to hold the entire list. David Carlisle's scheme defines as many macros as there are list items. I don't know which one, between this one and David Carlisle's solution, requires less resources.

\documentclass{minimal}
\usepackage{etoolbox}
\makeatletter
\newcount\listcount
\def\list@list{}
\def\do#1{%
  \advance\listcount\@ne
  \edef\list@list{%
    \unexpanded\expandafter{\list@list}%
    \the\listcount{\unexpanded{#1}}%
  }%
}
\forcsvlist\do{%
  first element,second element,third element,fourth element,fifth element
}
% \getelement{<number>}
\def\getelement#1{%
  \def\reserved@a##1#1##2##3\listmark{%
    \edef\reserved@a{\unexpanded{##2}}%
    \ifx\reserved@a\@nnil
      \@latexerr{No item number '#1'}\@ehd
    \else
      ##2%
    \fi
  }%
  \expandafter\reserved@a\list@list#1{\@nil}\listmark
}
\makeatother
\begin{document}
\begin{itemize}
\item The third element is: ``\getelement{3}''.
\item The fourth element is: ``\getelement{4}''.
\item The fourth element again: ``\getelement{4}''.
\item The fifth element is: ``\getelement{5}''.
% This gives error:
%\item The non-existent element is: ``\getelement{100}''.
\end{itemize}
\end{document}

If you were to prettify your list as, e.g.,

\forcsvlist\do{%
  first element  ,
  second element ,
  third element  ,
  fourth element ,
  fifth element
}

the spaces before the list items are rightly removed by \forcsvlist (via \@iden), but not the spaces after the list items. In that case, list normalization is required.

Even without prettifying the list, the space after fifth element is retained. Look at your output. To avoid the trailing space, add the comment sign at the end of the list: fifth element%.

EDIT

Here is a solution that normalizes the list and is expandable. I still prefer this iterative solution to defining as many commands as the number of list items.

\documentclass{minimal}
\usepackage{catoptions}
\makeatletter
\newcount\gnilistcount
% \addlistitems{<listcmd>}{<items>}
\def\addlistitems#1#2{%
  \ifdefTF#1{}{\def#1{}}%
  \cptfor#2\dofor{%
    \advance\gnilistcount\@ne
    \edef#1{%
      \unexpanded\expandafter{#1}%
      \the\gnilistcount{\unexpanded{##1}}%
    }%
  }%
}
% \getelement{<number>}{<listcmd>}
\def\getelement#1#2{%
  \expandafter\gni@getelement#2\@nil\@nil\listmark{#1}%
}
\def\gni@getelement#1#2#3\listmark#4{%
  \ifstrcmpTF{#1}\@nil{%
    \@latexerr{No item number '#4'}\@ehd
  }{%
    \ifnumcmpTF#1=#4{%
      #2%
    }{%
      \gni@getelement#3\listmark{#4}%
    }%
  }%
}
 \makeatother

% Examples:
\addlistitems\gnilist{%
  first element  ,
  second element ,
  third element  ,
  fourth element ,
  fifth element
}
% Get third element in an \edef:
\edef\x{\getelement{3}\gnilist}
%\show\x

\begin{document}
\begin{itemize}
\item The third element is: ``\getelement{3}\gnilist''.
\item The fourth element is: ``\getelement{4}\gnilist''.
\item The fourth element again: ``\getelement{4}\gnilist''.
\item The fifth element is: ``\getelement{5}\gnilist''.
% This gives error:
%\item The non-existent element is: ``\getelement{100}\gnilist''.
\end{itemize}
\end{document}

Here is a more general \addlistitems that also normalizes the list before saving it.

\documentclass{minimal}
\usepackage{catoptions}
\makeatletter
% \addlistitems[<optional parser>]{<listcmd>}{<items>}
% \addlistitems*[<optional parser>]{<listcmd>}{<itemcmd>}
% The same item may be entered more than once, but with different
% serial numbers. If this isn't the desired spec, then the OP should
% say so.
\robust@def*\addlistitems{\cpt@teststopt\gni@addlistitems,}
\robust@def*\gni@addlistitems[#1]#2#3{%
  \begingroup
  \ifdefTF#2{}{\def#2{}}%
  \edef\tempb{\cptremovescape#2}%
  \ifcsndefTF{listcount@\tempb}{}{%
    \csn@xdef{listcount@\tempb}{0}%
  }%
  \def\csv@do##1{%
    \aftercsname\cptpushnumber{listcount@\tempb}%
    \edef#2{%
      \unexpanded\expandafter{#2}%
      \usecsn{listcount@\tempb}{\unexpanded{##1}}%
    }%
  }%
  \edef\tempa{\csv@@parse\ifcpt@st*\fi}%
  \tempa[#1]{#3}%
  \postgroupdef#2\endgroup
}
% \getelementofnumber{<number>}{<listcmd>}
% This can be used for a general list command <listcmd>.
\new@def*\getelementofnumber#1#2{%
  \expandafter\gni@getelement#2\@nil\@nil\listmark{#1}%
}
\new@def*\gni@getelement#1#2#3\listmark#4{%
  \ifstrcmpTF{#1}\@nil{%
    \@latexerr{No item number '#4'}\@ehd
  }{%
    \ifnumcmpTF#1=#4{%
      #2%
    }{%
      \gni@getelement#3\listmark{#4}%
    }%
  }%
}
\makeatother

% Example:
\addlistitems\gnilist{%
  element 1 ,
  element 2 ,
  element 3 ,
  element 4 ,
  element 5
}
% The list separator is changed here:
\addlistitems[;]\gnilist{%
  element 6  ;
  element 7  ;
  element 8  ;
  element 9  ;
  element 10 ;
  % If you don't want 'element 10' entered twice, say so:
  element 10 ;
}
%\show\gnilist

% Get element no. 3 in an \edef:
\edef\x{\getelementofnumber{3}\gnilist}
%\show\x

\def\getel#1{\getelementofnumber{#1}\gnilist}

\begin{document}
\begin{itemize}
\item The third element is: ``\getel{3}''.
\item The fourth element is: ``\getel{4}''.
\item The fourth element again: ``\getel{4}''.
\item The fifth element is: ``\getel{5}''.
\item The ninth element is: ``\getel{9}''.
% This gives error:
%\item The non-existent element is: ``\getel{100}''.
\end{itemize}
\end{document}
4
  • Thanks for your answer. I actually don't understand everything in this code. That'll give me a little bit of study to do! BUT there's a slight problem (hope you'll be able to explain why this happens): tried to \edef\fourthelement{\getelement{4}} and this yields the same error as in the OP... Any clue? Nov 3, 2012 at 22:46
  • OK, I didn't realize that you needed an expandable solution. That will certainly require more resources, either David Carlisle's store-based approach or an iterative one. Mine will have to be an iterative one, which will be slower than David's solution.
    – Ahmed Musa
    Nov 3, 2012 at 22:51
  • Thanks for your edit, it's beautiful! I believe it will be a good resource for other people too! Is it possible to do the same without the package catoptions? Nov 4, 2012 at 10:15
  • Is it possible to do the same without the catoptions package? Of course, but not by me now.
    – Ahmed Musa
    Nov 4, 2012 at 16:16
1

With functional package, you can write the following code:

\documentclass{article}

\usepackage{functional}

\SeqNew \lMySeq
\SeqSetFromClist \lMySeq {
  first element,
  second element,
  third element,
  fourth element,
  fifth element
}

\PrgNewFunction \getnthelement {m} {%
  \SeqVarItem \lMySeq {#1}%
}

\PrgNewFunction \storenthelement {Mm} {%
  \TlSet #1 {\SeqVarItem \lMySeq {#2}}%
}

\storenthelement \fourthelement {4}

\begin{document}

\begin{itemize}
\item The third element is: ``\getnthelement{3}''.
\item The fourth element is: ``\fourthelement''.
\item The fourth element again: ``\fourthelement''.
\item The fifth element is: ``\getnthelement{5}''.
\end{itemize}

\end{document}

enter image description here

0

I know, the original question is quite old, but maybe someone's looking for a straight forward solution using a loop (from pgffor package, which is included in tikz) and a counter:

\documentclass{standalone}

\usepackage{pgffor}

\newcounter{itemcount}
\newcommand{\getitem}[2]{%
  \setcounter{itemcount}{0}%
  \foreach \i in #1 {%
    \ifnum\theitemcount=#2{\i}{}\fi%
    \stepcounter{itemcount}%
  }%
}

\def\mylist{a,b,c,d,e}

\begin{document}

  \getitem{\mylist}{0}
  \getitem{\mylist}{2}
  \getitem{\mylist}{4}
  \getitem{\mylist}{1}
  \getitem{\mylist}{3}

\end{document}

Output:

enter image description here

1
  • Welcome to TeX.SE!
    – Mensch
    Aug 23, 2022 at 9:22

You must log in to answer this question.

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