9

I would like LaTeX to "know" how many items there are in a certain list. I want this number to be stored in a variable. In such a way I hope that this variable is sampled during the first compilation and available for the next compilations. Then I can refer to the number of items in my text before the list appears in the same text. How can I accomplish that? My minimum example does not work. Thanks in advance :)

\documentclass[10pt,a4paper,twosided]{article}
\usepackage{etoolbox}

\begin{document}

%The following list contains \NumberOfItems\ items

\begin{enumerate} 

\item item A
\item item B
\item item C

\end{enumerate}
\newcommand{\NumberOfItems}{\value{enumi}}

The previous list contains \NumberOfItems\ items


\end{document}
lockstep
  • 250,273
myotis
  • 936
  • I believe that your question addresses the same problem as this one: http://tex.stackexchange.com/questions/74595/count-and-use-the-number-of-items-in-advance Please tell us if it is the case. If so, we could mark this as a duplicate. – yo' Feb 07 '13 at 11:40
  • @tohecz That question is specifically tailored for itemize. – egreg Feb 07 '13 at 11:42
  • Please, add a link to the other related question and add also the solution to your minimal example. – egreg Feb 08 '13 at 11:07
  • Are my changes what you asked for? – myotis Feb 08 '13 at 11:25
  • No, these aren't. However, you can exactly use \getrefnumber{firstlist} in place of your \NumberOfItems (requires the package refcount; firstlist is just the label assigned to the environment with enumeratecount) as I showed in my previous answer. – egreg Feb 08 '13 at 11:27
  • What's the problem with my solution in the previous answer? It does exactly what you're asking here. – egreg Feb 08 '13 at 12:03
  • Aha...I did not know the function "\getrefnumber{}". I agree, with that function I can use your former solution. Thanks ! : ) – myotis Feb 08 '13 at 12:36

4 Answers4

9

If you plan to use this only for first level lists and don't plan to use the enumitem package for personalizing the environment, using a \label in the last item is sufficient.

However a general solution that allows for rearrangement of items without bothering with the position of \label, is compatible also with enumitem and also works for nested list can be the following:

\documentclass[a4paper]{article}

\makeatletter
\newenvironment{enumeratecount}[1]
  {\def\thisenumeratecountlabel{#1}\enumerate}
  {\edef\@currentlabel{\number\value{\@enumctr}}%
   \label{\thisenumeratecountlabel}\endenumerate}
\makeatother

\begin{document}

The following list contains~\ref{firstlist} items, while
the nested list contains~\ref{innerlist}.

\begin{enumeratecount}{firstlist}
\item item A
\item item B
  \begin{enumeratecount}{innerlist}
  \item sub A
  \item sub B
  \end{enumeratecount}
\item item C
\end{enumeratecount}

The previous list contains \ref{firstlist} items, while the
inner list contains~\ref{innerlist}.

\end{document}

You just use \begin{enumeratecount}{<label>} for stating the label to be used (before or after the environment) for getting the number of items.

If you use enumitem you can even say

\begin{enumeratecount}{<label>}[<enumitem settings>]

so, for instance,

\begin{enumeratecount}{mylist}[label=\Alph*]

would work correctly (which wouldn't with the "simple method" outlined above and in Ulrike's answer).


The solution uses a couple of facts: a \label command refers to the current value of \@currentlabel, so before doing \endenumerate we define \@currentlabel to be the complete expansion of \value{\@enumctr}. The macro \@enumctr expands to enumi, enumii, enumiii or enumiv depending on the nesting level of the current enumerate environment; thus we're guaranteed to get the right number at all levels. Then we say \label{\thisenumeratecountlabel} that will set the label name to the original argument to enumeratecount. Locality of environments does the rest.


An extension

Suppose we want to say "the following list contains 1 item" or "the following list contains 3 items", adding the "s" conditionally.

This can be done with the help of the refcount package.

\usepackage{refcount}
\newcommand{\addphrase}[3]{% #1 = label, #2 = text if number >1, #3 = text if number =1
  \ifnum\getrefnumber{#1}>1
    #2%
  \else
    #3%
  \fi}

So the text above can be generated by

the following list contains \ref{firstlist}~\addphrase{firstlist}{items}{item}
egreg
  • 1,121,712
  • Thanks a lot ! How can I use \ref{firstlist} in a if-then-else decision? Something like \ifnumcomp{\ref{firstlist}{>}{1}{do this}{else, do that} – myotis Feb 07 '13 at 16:56
  • @EspenDonali I've added some code. – egreg Feb 07 '13 at 17:16
  • Thank again, but it is not exactly what i need. I need a new variable, made by \newcommand, with the value \ref{firstlist}, then I can use this variable in whatever commads from etoolbox. – myotis Feb 07 '13 at 17:20
  • @EspenDonali Please, make a new question, so that you can explain more clearly what you want, with some examples. – egreg Feb 07 '13 at 17:44
  • Sure : ), and thanks to both of you giving me answers thus far! – myotis Feb 08 '13 at 09:46
6

In your example already \ref gives the correct output:

\documentclass[10pt,a4paper,twosided]{article}
\usepackage{etoolbox}


\begin{document}

The following list contains \ref{a} items

\begin{enumerate} 

\item item A
\item item B
\item\label{a} item C

\end{enumerate}
\end{document}
Ulrike Fischer
  • 327,261
  • Not if the list is nested. – egreg Feb 07 '13 at 11:48
  • 3
    @egreg: I know. It also doesn't work if e.g. the numbering sheme is changed. That's why I wrote carefully "in the example ..." ;-). But I don't think that the question makes sense with nested lists. Would you write "The sub list of the second item hat 3 items?". – Ulrike Fischer Feb 07 '13 at 11:56
6

Q: How can I count the number of items and store this number in a command by Q:\newcommand?

A: Add a counter to item

\let\olditem\item
\AtBeginEnvironment{enumerate}{
 \renewcommand{\item}{%
 \olditem%
 \stepcounter{mytempnumber}%
}}

As always one cannot redefine a macro recursive (that's why we need \let). I would like to recommend to use a counter for a number, not a macro.

Q: How can this be done, so that the value of this variable carries over to the next compilation where it can be available for "My conditional text" ?

A: Write it out! You can do this as value only, or as assignment:

\AtEndDocument{%
 \openoutputfile{\jobname.num}{number}
 \addtostream{number}{%%
 \noexpand\setcounter{mynumber}{\themytempnumber}} 
 \closeoutputstream{number}
}

We need to handle expansion carefully here, since everything is expanded when wrote to a file.

Here's the complete code:

\documentclass{article}
\usepackage{etoolbox}

\usepackage{newfile}

\newoutputstream{number}

\newcounter{mynumber}
\newcounter{mytempnumber}

\let\olditem\item
\AtBeginEnvironment{enumerate}{
 \renewcommand{\item}{%
 \olditem%
 \stepcounter{mytempnumber}%
}}

\AtEndDocument{%
\openoutputfile{\jobname.num}{number}
\addtostream{number}{%%
 \noexpand\setcounter{mynumber}{\themytempnumber}} 
\closeoutputstream{number}
}

\AtBeginDocument%
{%
\IfFileExists{\jobname.num}{\input{\jobname.num}}{\message{NO \jobname.num rerun!}}%
}

\begin{document}

\themynumber% use as normal counter

\begin{enumerate} 
\item Issue A
\item Issue B
\item Issue C
\item 
\end{enumerate}

\end{document}

I used etoolbox to hook into the enumerate environment and newfile to do the i/o stuff but more convenient. A interested user will notice, that the file <main file name>.num contents only one line of code: \setcounter{mynumber}{4}. So it's important to include the file before the usage of the counter.

bloodworks
  • 10,178
  • Thanks for your thorough anwer! I will study it to see if I understand it, and then possibly implement it :) – myotis Feb 08 '13 at 13:59
2

I used the ifthen package instead of etoolbox as I am more familiar with the former; there is likely a similar construct in the latter. Create a new counter, say itemnum. Set the value of the counter at then end of your enumerate command. Then a little trickery: take 1 from the counter, then refstepcounter it, which adds one back and allows you to save its value in a label. The \ifthenelse command uses its value and compares it to 1.

This, of course, takes 2 runs to take effect as does any \label & \ref combination.

\documentclass[10pt,a4paper,twosided]{article}
\usepackage{ifthen}

\newcounter{itemnum}

\begin{document}

This report will addresses the following 
\ifthenelse{\ref{label:itemnum}=1}{issue}{issues}.


\begin{enumerate} 

\item Issue A
\item Issue B
\item Issue C
\setcounter{itemnum}{\value{enumi}}
\addtocounter{itemnum}{-1}
\refstepcounter{itemnum}\label{label:itemnum}

\end{enumerate}



\end{document}
GregH
  • 3,499