# Get n-th element of a list (with etoolbox, or not)

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

-
how long are your lists? (in particular are they longer than 9) – David Carlisle Nov 2 '12 at 18:14
@DavidCarlisle They will have ca 30 elements – gniourf_gniourf Nov 2 '12 at 18:30
Possible Duplicate: Macro to access a specific member of a list. – Peter Grill Nov 2 '12 at 18:31
shame:-) the answer will work fine for that, with less than 9 there are fancy tricks with #### one can play which are more fun:-) – David Carlisle Nov 2 '12 at 18:32

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}

-
Yes, that works, thanks! – gniourf_gniourf Nov 2 '12 at 18:36
Accepting this answer. It works flawlessly and my colleagues will be happy colleagues! – gniourf_gniourf Nov 2 '12 at 19:18

Here's a possible solution with xparse:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\newgniourflist}{ m }
{
\seq_new:c { g_gniourf_#1_seq }
}
\newgniourflist{gniourflist}
{
\seq_gput_right:cn { g_gniourf_#1_seq } { #2 }
}
\NewDocumentCommand{\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}

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

-
Wow, LaTeX3 stuff... I'm having errors when compiling: ! Undefined control sequence. \\getnthelement #1#2->\seq_item:cn – gniourf_gniourf Nov 2 '12 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 '12 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). – gniourf_gniourf Nov 2 '12 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 '12 at 18:55
Yes, you are right, they are! Everything you say here is, unfortunately, absolutely true! – gniourf_gniourf Nov 2 '12 at 18:58

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{%
\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
\ifdefTF#1{}{\def#1{}}%
\cptfor#2\dofor{%
\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:
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
% 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.
\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:
element 1 ,
element 2 ,
element 3 ,
element 4 ,
element 5
}
% The list separator is changed here:
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}

-
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? – gniourf_gniourf Nov 3 '12 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 '12 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`? – gniourf_gniourf Nov 4 '12 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 '12 at 16:16