9

I am trying to write a macro that selects an item from a comma separated list, \select{2}{A,B,C,D,E} results in B. I am using the xfor package to process the list, but would try etoolbox if that offered a better solution. Now for the problem: the macro works fine for plain text, however if the item is a number, I get an unclosed \ifnum.

! Incomplete \ifnum; all text was ignored after line 29.
 
                \fi 

Here is the minimal example

\documentclass{article}
\usepackage{xfor}
\usepackage{biblatex}

\makeatletter
\newcount\select@count
\newcount\select@i
\newcommand\select[2]{%
    \select@i=1\select@count=0%
    \@for\@@select@item:={#2}\do{\advance\select@count by1}%
    \ifnum#1>\select@count??\else%
    \@for\@@select@item:={#2}\do{%
      \advance\select@i by1%
      \ifnum#1<1??\@endfortrue\fi%
      \ifnum\select@i=#1\@@select@item\@endfortrue\fi%
    }%
  \fi%
}
\makeatother

\begin{document}
\begin{itemize}
  \item \verb+\select{0}{A,B,C,D,E}+ gives \select{0}{A,B,C,D,E}
  \item \verb+\select{1}{A,B,C,D,E}+ gives \select{1}{A,B,C,D,E}
  \item \verb+\select{2}{A,B,C,D,E}+ gives \select{2}{A,B,C,D,E}
  \item \verb+\select{5}{A,B,C,D,E}+ gives \select{5}{A,B,C,D,E}
  \item \verb+\select{10}{A,B,C,D,E}+ gives \select{10}{A,B,C,D,E}
%  these do not work
%  \item \verb+\select{1}{1,2,3,4,5}+ gives \select{1}{1,2,3,4,5}
%  \item \verb+\select{2}{1,2,3,4,5}+ gives \select{2}{1,2,3,4,5}
\end{document}

Any thought on why it fails with numbers? Do I need to protect my list?

4 Answers 4

11

The main error is in the line

\ifnum\select@i=#1\@@select@item\@endfortrue\fi%

When \@@select@item the lookup for the number is not finished and TeX keeps expanding until finding something that's not a digit; when \@@select@item is 1, it's easy to predict chaos. A better style is

\ifnum#1=\select@i\@@select@item\@endfortrue\fi

because \select@i is a counter, so the <number> it represents is complete.

However you're off by one: in case of non numeric input the premature expansion of \@@select@item finds the preceding item.

Here's a working version; in order to avoid problems with expansion following a constant it's better to use \@ne for the constant 1 and \z@ for the constant 0.

\documentclass{article}
\usepackage{xfor}

\makeatletter
\newcount\select@count
\newcount\select@i
\newcommand\select[2]{%
    \select@i=\z@\select@count=\z@
    \@for\@@select@item:={#2}\do{\advance\select@count by\@ne}%
    \ifnum#1>\select@count??\else
      \@for\@@select@item:={#2}\do{%
        \advance\select@i by\@ne
        \ifnum#1<1??\@endfortrue\fi
        \ifnum#1=\select@i\@@select@item\@endfortrue\fi
      }%
  \fi
}
\makeatother

\begin{document}
\begin{itemize}
  \item \verb+\select{0}{A,B,C,D,E}+ gives \select{0}{A,B,C,D,E}
  \item \verb+\select{1}{A,B,C,D,E}+ gives \select{1}{A,B,C,D,E}
  \item \verb+\select{2}{A,B,C,D,E}+ gives \select{2}{A,B,C,D,E}
  \item \verb+\select{5}{A,B,C,D,E}+ gives \select{5}{A,B,C,D,E}
  \item \verb+\select{10}{A,B,C,D,E}+ gives \select{10}{A,B,C,D,E}
  \item \verb+\select{0}{1,2,3,4,5}+ gives \select{0}{1,2,3,4,5}
  \item \verb+\select{1}{1,2,3,4,5}+ gives \select{1}{1,2,3,4,5}
  \item \verb+\select{2}{1,2,3,4,5}+ gives \select{2}{1,2,3,4,5}
  \item \verb+\select{5}{1,2,3,4,5}+ gives \select{5}{1,2,3,4,5}
  \item \verb+\select{10}{1,2,3,4,5}+ gives \select{10}{1,2,3,4,5}
\end{itemize}
\end{document}

Note that the initial setting of \select@i is at 0.

enter image description here

An alternative version with only one counter and only one cycle:

\makeatletter
\newcount\select@count
\newcommand\select[2]{%
  \ifnum#1<\@ne??\else
  \select@count=\z@
  \@for\@@select@item:={#2,\@@nil}\do{%
    \advance\select@count\@ne
    \expandafter\ifx\@@select@item\@@nil
      ??\@endfortrue
    \else
      \ifnum#1=\select@count
        \@@select@item\@endfortrue
      \fi
    \fi}%
  \fi
}
\makeatother

The inevitable LaTeX3 solution, which has a big advantage on the \@for based ones: it's fully expandable.

\documentclass{article}

\usepackage{xparse}
\ExplSyntaxOn
\DeclareExpandableDocumentCommand \select { m m }
 {
  \egreg_select:nn { #1 } { #2 }
 }

\cs_new:Npn \egreg_select:nn #1 #2
 {
  \bool_if:nTF
   {
    \int_compare_p:n { #1 < 1 }
    ||
    \int_compare_p:n { #1 > \clist_count:n { #2 } }
   }
   {
    ??
   }
   {
    \clist_item:nn { #2 } { #1 }
   }
 }
\ExplSyntaxOff

\begin{document}
\begin{itemize}
  \item \verb+\select{0}{A,B,C,D,E}+ gives \select{0}{A,B,C,D,E}
  \item \verb+\select{1}{A,B,C,D,E}+ gives \select{1}{A,B,C,D,E}
  \item \verb+\select{2}{A,B,C,D,E}+ gives \select{2}{A,B,C,D,E}
  \item \verb+\select{5}{A,B,C,D,E}+ gives \select{5}{A,B,C,D,E}
  \item \verb+\select{10}{A,B,C,D,E}+ gives \select{10}{A,B,C,D,E}
  %these do not work
  \item \verb+\select{1}{1,2,3,4,5}+ gives \select{1}{1,2,3,4,5}
  \item \verb+\select{2}{1,2,3,4,5}+ gives \select{2}{1,2,3,4,5}
\end{itemize}

% Let's show it's fully expandable!
\edef\x{\select{2}{1,2,3,4,5}}\x

\end{document}

enter image description here


If you want to give a macro for the second argument, you need to expand it before the functions come into action.

\documentclass{article}

\usepackage{xparse}
\ExplSyntaxOn
\DeclareExpandableDocumentCommand \select { m m }
 {
  \egreg_select:on { #2 } { #1 }
 }

\cs_new:Npn \egreg_select:nn #1 #2
 {
  \bool_if:nTF
   {
    \int_compare_p:n { #2 < 1 }
    ||
    \int_compare_p:n { #2 > \clist_count:n { #1 } }
   }
   {
    ??
   }
   {
    \clist_item:nn { #1 } { #2 }
   }
 }
\cs_generate_variant:Nn \egreg_select:nn { o }
\ExplSyntaxOff

\newcommand{\alist}{X,Y,Z}

\begin{document}
\begin{itemize}
  \item \verb+\select{0}{A,B,C,D,E}+ gives \select{0}{A,B,C,D,E}
  \item \verb+\select{1}{A,B,C,D,E}+ gives \select{1}{A,B,C,D,E}
  \item \verb+\select{2}{A,B,C,D,E}+ gives \select{2}{A,B,C,D,E}
  \item \verb+\select{5}{A,B,C,D,E}+ gives \select{5}{A,B,C,D,E}
  \item \verb+\select{10}{A,B,C,D,E}+ gives \select{10}{A,B,C,D,E}
  %these do not work
  \item \verb+\select{1}{1,2,3,4,5}+ gives \select{1}{1,2,3,4,5}
  \item \verb+\select{2}{1,2,3,4,5}+ gives \select{2}{1,2,3,4,5}
\end{itemize}

\verb+\select{2}{\alist}+ gives \select{2}{\alist}

% Let's show it's fully expandable!
\edef\x{\select{2}{1,2,3,4,5}}\x

\end{document}

Note that for efficiency I inverted the order of the arguments in \egreg_select:nn (the first is the list, the second is the number; but this doesn't change the syntax for the user level command.

8
  • I've been too slow...
    – cgnieder
    Nov 8, 2013 at 18:19
  • Where does one find/learn the latex3 commands like \clist_count, etc.?
    – user39714
    Nov 10, 2013 at 15:04
  • @user39714 texdoc interface3 on your machine
    – egreg
    Nov 10, 2013 at 15:05
  • I still have alot of trouble getting my head around expansions. I tried to replace the clist with a cs (\def\myList{1,2,3,4,5}) and \select{2}{\myList}, which does not work because it thinks there is only one item?
    – user39714
    Nov 11, 2013 at 12:51
  • @user39714 You can change \clist_item:nn into \clist_item:on and add, just before \ExplSyntaxOff, the magic line \cs_generate_variant:Nn \clist_item:nn { o }
    – egreg
    Nov 11, 2013 at 12:54
8

Here is an etoolbox implementation:

enter image description here

\documentclass{article}
\usepackage{etoolbox}% http://ctan.org/pkg/etoolbox

\makeatletter
\newif\ifprinted
\newcounter{select@i}
\newcommand\select[2]{% \select{<num>}{<csv list>}
  \printedfalse% Nothing has been printed
  \setcounter{select@i}{0}% List starts from 1
  \renewcommand*{\do}[1]{%
    \stepcounter{select@i}% Next item
    \ifnum\value{select@i}=#1\relax##1\printedtrue\fi}% How each item should be processed
  \docsvlist{#2}% Process list
  \ifprinted\else??\fi% No number was printed
}
\makeatother

\begin{document}
\begin{itemize}
  \item \verb+\select{0}{A,B,C,D,E}+ gives \select{0}{A,B,C,D,E}
  \item \verb+\select{1}{A,B,C,D,E}+ gives \select{1}{A,B,C,D,E}
  \item \verb+\select{2}{A,B,C,D,E}+ gives \select{2}{A,B,C,D,E}
  \item \verb+\select{5}{A,B,C,D,E}+ gives \select{5}{A,B,C,D,E}
  \item \verb+\select{10}{A,B,C,D,E}+ gives \select{10}{A,B,C,D,E}
  \item \verb+\select{1}{5,4,3,2,1}+ gives \select{1}{5,4,3,2,1}
  \item \verb+\select{2}{5,4,3,2,1}+ gives \select{2}{5,4,3,2,1}
\end{itemize}
\end{document}

There is no need to count the number of items in the list. Only print out ?? if \printedfalse (no number has been printed).

7

I suggest l3clist which provides the function \clist_item:Nn. A list starts add the indice 1.

\documentclass{article}

\usepackage{biblatex}

\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \select { m m }
 {
  \clist_item:nn { #2 } { #1 + 1 }
 }
\ExplSyntaxOff

\begin{document}
\begin{itemize}
  \item \verb+\select{0}{A,B,C,D,E}+ gives \select{0}{A,B,C,D,E}
  \item \verb+\select{1}{A,B,C,D,E}+ gives \select{1}{A,B,C,D,E}
  \item \verb+\select{2}{A,B,C,D,E}+ gives \select{2}{A,B,C,D,E}
  \item \verb+\select{5}{A,B,C,D,E}+ gives \select{5}{A,B,C,D,E}
  \item \verb+\select{10}{A,B,C,D,E}+ gives \select{10}{A,B,C,D,E}
  %these do not work
  \item \verb+\select{1}{1,2,3,4,5}+ gives \select{1}{1,2,3,4,5}
  \item \verb+\select{2}{1,2,3,4,5}+ gives \select{2}{1,2,3,4,5}
\end{itemize}
\end{document}
0

You can use macros from xinttools to accomplish this in an expandable way.

\documentclass{article}

\usepackage{xinttools}% \http://ctan.org/pkg/xint

% if #1 is too big, returns nothing; if #1 is < 0, returns from the tail
% if #1=0 returns the number of items

\newcommand{\select}[2]{\xintNthElt {#1}{\xintCSVtoList{#2}}}

\begin{document}\thispagestyle{empty}
\begin{itemize}
  \item \verb+\select{0}{A,B,C,D,E}+ gives \select{0}{A,B,C,D,E}
  \item \verb+\select{1}{A,B,C,D,E}+ gives \select{1}{A,B,C,D,E}
  \item \verb+\select{2}{A,B,C,D,E}+ gives \select{2}{A,B,C,D,E}
  \item \verb+\select{5}{A,B,C,D,E}+ gives \select{5}{A,B,C,D,E}
  \item \verb+\select{10}{A,B,C,D,E}+ gives \select{10}{A,B,C,D,E}
%  these do  work
 \item \verb+\select{1}{1,2,3,4,5}+ gives \select{1}{1,2,3,4,5}
 \item \verb+\select{2}{1,2,3,4,5}+ gives \select{2}{1,2,3,4,5}
\end{itemize}
\end{document}

select

You must log in to answer this question.

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