34

When writing LaTeX code, I often successively make changes like adding or removing items. I'd like to have TeX count and update the number of items in the list automatically, as in

There are \numItemsNextList ways how a parameter can be given:
\begin{itemize}
\item By a constant expression.
\item By user interaction.
\item From another database.
\end{itemize}

(what happens next is of course that I realize there is yet another way to give a parameter and add it to the list)

I could roll out my own preprocessor to just count the number of \item-commands in the following \begin{...} ... \end{...} block. But is there a TeX solution as well?

20

Here's a way: we modify the itemize environment with etoolbox tools; each \item command will step a counter; every environment steps another. A couple of LaTeX runs can be necessary for the numbers to stabilize.

\documentclass{article}
\usepackage{etoolbox,refcount}

\newcounter{countitems}
\newcounter{nextitemizecount}
\newcommand{\setupcountitems}{%
  \stepcounter{nextitemizecount}%
  \setcounter{countitems}{0}%
  \preto\item{\stepcounter{countitems}}%
}
\makeatletter
\newcommand{\computecountitems}{%
  \edef\@currentlabel{\number\c@countitems}%
  \label{countitems@\number\numexpr\value{nextitemizecount}-1\relax}%
}
\newcommand{\nextitemizecount}{%
  \getrefnumber{countitems@\number\c@nextitemizecount}%
}
\makeatother
\AtBeginEnvironment{itemize}{\setupcountitems}
\AtEndEnvironment{itemize}{\computecountitems}

\begin{document}

There are \nextitemizecount{} ways how a parameter can be given:
\begin{itemize}
\item By a constant expression.
\item By user interaction.
\item From another database.
\end{itemize}

There are \nextitemizecount{} ways how a parameter can be given:
\begin{itemize}
\item By a constant expression.
\item By user interaction.
\item From another database.
\item Foo.
\end{itemize}

The next doesn't count.

\begin{itemize}
\item A
\item B
\end{itemize}

There are \nextitemizecount{} ways how a parameter can be given:
\begin{itemize}
\item By a constant expression.
\item By user interaction.
\end{itemize}

\end{document}

enter image description here

  • Chose this one because it precisely fits my request. Thanks a lot for the more complete solution below, too! – srs Oct 1 '12 at 20:25
  • Great! Is there a way to make it consistent over beamer overlays? Using \begin{itemize}[<+->] yields the right value for \nextitemizecount, but only on the first slide :\ – iago-lito Nov 28 '17 at 11:42
  • @iago-lito I’m afraid beamer is using quite a different model – egreg Nov 28 '17 at 13:34
24

I am not sure how to do it with itemize. But with enumerate environment you could add a label to last item.

\documentclass{article}

\begin{document}
There are \ref{lst:num} ways how a parameter can be given:
\begin{enumerate}
    \item By a constant expression.
    \item By user interaction.
    \item From another database. 
    \label{lst:num}
\end{enumerate}

\end{document}
  • +1 This is the only solution so far that provides a correct count with nested lists. – Peter Grill Oct 1 '12 at 2:29
  • @PeterGrill now this has changed; another solution also works with nested lists. – Gonzalo Medina Oct 1 '12 at 3:46
  • 1
    A drawback of this solution (and others proposed here) is that it is conceptually strange to refer to the last item in a list in order to count the number of items (the count has not much to do with the last item in particular). As an illustration of that problem, hyperref will create a link from the printed count to the last item. Any idea how to prevent the count to generate such a link? (Without disabling every links of course.) – Olivier Cailloux Nov 4 '14 at 17:28
18

With the help of the enumitem package you can define a list based on enumerate, but behaving as itemize as far as labels are concerned. In this way one is allowed to place \labels, in particular to the last item. The following example illustrates such definition (for a list environment with four-level nesting) and also shows that the approach works with nesting:

\documentclass{article}
\usepackage{enumitem}

\newlist{citemize}{enumerate}{4}
\setlist[citemize,1]{label=$\bullet$,ref=\arabic*}
\setlist[citemize,2]{label=--,ref=\arabic*}
\setlist[citemize,3]{label=$\ast$,ref=\arabic*}
\setlist[citemize,4]{label=$\cdot$,ref=\arabic*}

\begin{document}

There are~\ref{lst:num} ways how a parameter can be given, and user interaction has~\ref{lst:subnum} possibilities:
\begin{citemize}
  \item By a constant expression.
  \item By user interaction.
  \begin{citemize}
    \item First subitem.
    \item Second subitem.
    \label{lst:subnum}
  \end{citemize}
  \item From another database. 
  \label{lst:num}
\end{citemize}

\end{document}

enter image description here

  • This is what I had in mind but did not know how to implement. – mythealias Oct 1 '12 at 3:49
  • Yep, this also works with nested lists, and provides a count of an itemized list. – Peter Grill Oct 1 '12 at 3:51
4

The following code provides a redefined itemize environment.

Update

An answer that suits your request a little better …
\items are labelable if you use the option "Lableable Items" (\item[<opt>]\label{<key>}).

Code

\documentclass{article}
\usepackage{xspace}         % used in the \numItemsNextList command: http://ctan.org/pkg/xparse
\newcounter{myItemCounter}          % counts items in the itemize list
\newcounter{myInternalLabelCounter} % increments so that every itemize environment gets it own label
\let\olditem\item               % save original commands: \item
\let\olditemize\itemize         %                         \begin{itemize}
\let\oldenditemize\enditemize   %                         \end{itemize}

\newif\ifcountitems             % \numItemsNextList set countitems true, otherwise no counting occurs

\newcommand*\numItemsNextList{%
    \countitemstrue%                                         initial: set countitems=true
    \setcounter{myItemCounter}{0}%                           initial: myItemCounter=0
    \stepcounter{myInternalLabelCounter}%                    initial: myInternalLabelCounter++
    \ref{myInternalLabelKey\arabic{myInternalLabelCounter}}% needs 2nd run
    \xspace%                                                 provides space without the need to use \numItemsNextList{}
}
\renewenvironment{itemize}{%
  \olditemize%
}{%
  \ifcountitems% only set label if we count at all
%    \addtocounter{myItemCounter}{-1}% Without "Labelable Items": comment out if you have labelable items
%    \refstepcounter{myItemCounter}%   Without "Labelable Items": comment out if you have labelable items
    \label{myInternalLabelKey\arabic{myInternalLabelCounter}}%
  \fi%
  \oldenditemize%
  \global\countitemsfalse% sets countitems=false (end of itemize environment)
}
\renewcommand*\item{%
  \ifcountitems%
%  \stepcounter{myItemCounter}%     Without "Labelable Items": If you use this comment out next line!
  \refstepcounter{myItemCounter} % With "Labelable Items":    If you use this comment out the line above!
  \fi%
  \olditem%
}

\begin{document}
There are \numItemsNextList ways how a parameter can be given:
\begin{itemize}
\item By a constant expression.
\item By user interaction.
\item From another database.
\end{itemize}

Without I:
\begin{itemize}
\item Not countable.
\end{itemize}

There are \numItemsNextList ways how a parameter can be given (\#\ref{lstitm:important} is a good one.):
\begin{itemize}
\item By a constant expression.
\item By user interaction.
\item\label{lstitm:important} From another database.
\item (Not really.)
\end{itemize}

Without II:
\begin{itemize}
\item Not countable.
\item Not countable.
\end{itemize}
\end{document}

Output

enter image description here

Old Solution

This solution can refer to any itemize environment with the optional argument.

Code

\documentclass{article}
\newcounter{myItemCounter}
\let\olditem\item
\let\olditemize\itemize
\let\oldenditemize\enditemize

\makeatletter
\newcommand*\@empty@string{}
\newcommand*\count@label{}
\renewcommand*\item{\refstepcounter{myItemCounter}\olditem}

\renewenvironment{itemize}[1][]{%
    \setcounter{myItemCounter}{0}%
    \renewcommand*\count@label{#1}%
    \olditemize%
    }{%
    \ifx\count@label\@empty@string\else%
        \label{\count@label}%
    \fi%
    \oldenditemize%
}
\newcommand*{\labelitem}[1]{%
    \refstepcounter{myItemCounter}\label{#1}%
    \olditem%
}
\makeatother

\begin{document}
This list contains \ref{lst:ct} item(s), the \ref{lst:important}. is important:
\begin{itemize}
    \item[a] First item.
    \labelitem{lst:important}[x] Next item.
    \labelitem{lst:ct} Last item?
\end{itemize}

This list contains \ref{lst:ct2} item(s):
\begin{itemize}[lst:ct2]
    \item First item.
    \item[i] Last item?
\end{itemize}
\end{document}

Output

compiled code

2

The package xcntperchap can do this, with some 'easy' setup, using a dummy track level counter (dummycntr). Since itemize does not use a counter itself, another counter is necessary (for enumerate, it would be much easier, just say \RegisterCounters{dummycntr}{enumi} then.

The current version works for the first level of itemize nesting only so far.

\documentclass{article}

\usepackage{xpatch}
\usepackage{xcntperchap}

\newcounter{dummycntr}
\newcounter{itemi}
\RegisterCounters{dummycntr}{itemi}
\xpretocmd{\item}{\stepcounter{itemi}}{}{}
\xpretocmd{\itemize}{\stepcounter{dummycntr}}{}{}


\begin{document}
There are \ObtainTrackedValue[1]{dummycntr}{itemi} ways how a parameter can be given, but see also that there are 
\ObtainTrackedValue[2]{dummycntr}{itemi} other ways: 

\begin{itemize}
\item By a constant expression.
\item By user interaction.
\item From another database.
\end{itemize}

Other ways:

\begin{itemize}
\item This
\item Is 
\item Some
\item Other
\item Itemization
\end{itemize}




\end{document}

enter image description here

A future version of xcntperchap will support such features more easily!

0

I would like to add how to extend mythealias' and Gonzalo Medina's (great) answers for the case that you have to make calculations with the resulting number (my addendum is based upon another question).

To illustrate that computations might make sense, consider the case that a single list contains entries that had to be counted separately.

\documentclass{article}

\usepackage{enumitem}
\usepackage{hyperref}
\usepackage{pdfpages} % this is actually required, but I did not figure out yet why. If you do, please let me know :)

\begin{document}

% citemize simulates compactitem
\newlist{citemize}{enumerate}{1}
\setlist[citemize,1]{nosep,label=\textbullet,ref=\arabic*}

\newcounter{SizeBlockOne}
\setcounter{SizeBlockOne}{\getrefnumber{end-block-1}}
\newcounter{SizeBlockTwo}
\setcounter{SizeBlockTwo}{\getrefnumber{end-block-2}}
\setcounter{SizeBlockTwo}{%
   \numexpr\value{SizeBlockTwo}-\value{SizeBlockOne}%
}

The first block contains \theSizeBlockOne{} elements, the second block \theSizeBlockTwo{} elements.

\begin{citemize}
  \item entry 1 
  \item entry 2 \label{end-block-1}
  \item entry a 
  \item entry b 
  \item entry c \label{end-block-2}
\end{citemize}

\end{document}

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.