# Associate counters — stepping (many) counters simultaneously

This is (perhaps) not a real question and there maybe already an answer to my proposition/request.

The situation

There are packages that provide the number of the last page (i.e. lastpage or the 'newer' pageslts package), the 1st does not guarantee that it really holds the total number of pages, especially after a \pagenumbering{} command, the 2nd one provides this feature (according to documentation)

totcount is a very useful package for total values of counters, but fails for resets or other manipulation of counters, especially for pages or \chapters etc., when \appendix or similar commands are used.

I thought, it would be quite nice, that there would be another, associated counter to page (or chapter etc.), say totalpages (or totalchapters) which is automatically increased as well when \stepcounter{page} is issued, such that even a resetting of page counter has no influence on totalpages at all. The associated counters ideally be \newtotcounters from totcount package, but this is not mandatory.

There might be coding situations, where the driver counter and the associated counters have different values, but still are stepped at the same time.

There are some related questions,

I provide a proposition to this, using lists from etoolbox and a redefined \stepcounter command, that runs through a list and steps the associated counters too.

\documentclass{book}%

\usepackage{etoolbox}%
\usepackage{totcount}%

% Some packages only for output and dummy pages
\usepackage{blindtext}%
\usepackage{forloop}%
\usepackage{xcolor}%

\newcounter{loopcounter}%

\makeatletter

\let\LaTeXStandardStepCounter\stepcounter%

% This command defines a list for
\newcommand{\DeclareAssociatedCounters}[2]{%
% #1 --> driver counter
% #2 --> CSV list of other counters, that should be stepped, if the driver counter is stepped
%
\csgdef{@#1AssociatedList}{}%   Define some global list
}%

\newcommand{\stepassociatedcounter}[1]{%  A wrapper, if the list command has to be extended later on
\LaTeXStandardStepCounter{#1}%
}%

\renewcommand{\stepcounter}[1]{%
\LaTeXStandardStepCounter{#1}%   Traditional behaviour, since this is expected!
\ifcsdef{@#1AssociatedList}{%    Check first, whether the list exists at all,
\forlistcsloop{\stepassociatedcounter}{@#1AssociatedList}%  March through the list
}{% No list -> do nothing at all
}%
}%

\makeatother

\newcommand{\ShowNiceCounterOutput}[5]{%
\begin{center}%
\begin{tabular}{llll}%
& & & \tabularnewline
& \multicolumn{3}{c}{totcount page values} \tabularnewline
Page & \textcolor{red}{#1} & \textcolor{blue}{#2} & \textcolor{gray}{#3} \tabularnewline
\thepage & \textcolor{red}{\number\totvalue{#1}} & \textcolor{blue}{\number\totvalue{#2}} & \textcolor{gray}{\number\totvalue{#3}} \tabularnewline
& & & \tabularnewline
& \multicolumn{3}{c}{totcount section values} \tabularnewline
Section & \textcolor{red}{#4} & \textcolor{blue}{#5} \tabularnewline
\thesection & \textcolor{red}{\number\totvalue{#4}} & \textcolor{blue}{\number\totvalue{#5}} & \tabularnewline
&  & & \tabularnewline
\end{tabular}
\end{center}%
}%

\begin{document}
\regtotcounter{page} % Register a total value counter --> this will be the driver counter
\newtotcounter{totalpages}% 1st driven counter
\newtotcounter{anotherpagescounter}% 2nd driven counter

\regtotcounter{section}%
\newtotcounter{totalsections}

\DeclareAssociatedCounters{page}{totalpages,anotherpagescounter}%  Register the driver and the driven counters
\DeclareAssociatedCounters{section}{totalsections}%  Register the driver and the driven counters for sections%

\pagenumbering{Roman}%

\chapter{The first chapter}%

\section{My first section}%

% Generate dummy output
\forloop{loopcounter}{1}{\value{loopcounter} < 11}{%
\ShowNiceCounterOutput{page}{totalpages}{anotherpagescounter}{section}{totalsections}%

\blindtext%
\newpage%  10 pages!
}%

\section{My second section}%

% Generate dummy output, again ;-)

\forloop{loopcounter}{1}{\value{loopcounter} < 11}{%
\pagenumbering{arabic}%  -> pagenumber reset to zero , on purpose inside the loop
\ShowNiceCounterOutput{page}{totalpages}{anotherpagescounter}{section}{totalsections}%

\blindtext%
\newpage%  10 pages!
}%

\pagenumbering{arabic}%  -> pagenumber reset to zero
\appendix

\chapter{First Appendix chapter}%

\section{My (only) appendix section}%

% Generate dummy output, again ;-)

\forloop{loopcounter}{1}{\value{loopcounter} < 11}{%
\ShowNiceCounterOutput{page}{totalpages}{anotherpagescounter}{section}{totalsections}%

\blindtext%
\newpage%  10 pages!
}%

\end{document}


The MWE resets the page numbering many times, as well the section counting do to \chapters etc. The command \DeclareAssociateCounters provides a list of counters 'bound' to the driver counter. The screen shot shows, that even the resetting of driver counters does not affect the associated counters total values. In the example, there are 30 pages and three sections by setup, those numbers are reported by the \totvalue command applied to the associated counters.

For sure, a manipulation of the driver or the associated counter values by \setcounter or \addtocounter will of course lead to deviating values.

Are there other approaches, packages, that handle simultaneous stepping of counters (better) already?

I don't think, that aliascnt package by H. Oberdiek addresses the same issue.

• There should be a check, whether there is already a list and if that is the case, the counters should be appended to it or even removed (this still has to be programmed) – user31729 Aug 11 '14 at 0:42
• Did you have a look at zref? – cgnieder Aug 11 '14 at 7:16
• @cgnieder: Actually I have looked into that, it provides the total page counter solution, but not the associate counter feature itself, or did I skip something related in the manual of zref? – user31729 Aug 11 '14 at 8:08
• I vaguely remember that it allows to define counter properties. Maybe one can define something like it. (I'm not sure as I've never used zref myself...) – cgnieder Aug 11 '14 at 8:38
• @cgnieder: Yes, I have read about that counter module. It just adds a counter to the main properties list of the document, but there is no such thing as co-marching counters, although I appreciate the package as very, very useful – user31729 Aug 11 '14 at 8:39

Just for completeness, here's an implementation using expl3:

\documentclass{book}
\usepackage{xparse}

\usepackage{blindtext}
\usepackage{xcolor}
\usepackage{totcount}

\ExplSyntaxOn

\NewDocumentCommand{\DeclareAssociatedCounters}{mm}
{
\hupfer_declareassociatedcounters:nn { #1 } { #2 }
}
{
\hupfer_addassociatedcounters:nn { #1 } { #2 }
}
\NewDocumentCommand{\RemoveAssociatedCounters}{mm}
{
\hupfer_removeassociatedcounters:nn { #1 } { #2 }
}
\NewDocumentCommand{\ClearAssociatedCountersList}{m}
{
\hupfer_declareassociatedcounters:nn { #1 } { }
}

\cs_new_protected:Nn \hupfer_declareassociatedcounters:nn
{
\clist_clear_new:c { g_hupfer_bound_counters_#1_clist }
\hupfer_addassociatedcounters:nn { #1 } { #2 }
}

{
\clist_gput_right:cn { g_hupfer_bound_counters_#1_clist } { #2 }
% or a slower routine if we want to check the items are counter names
%\clist_map_inline:nn { #2 }
% {
%  \cs_if_exist:cTF { c@##1 }
%   {
%    \clist_gput_right:cn { g_hupfer_bound_counters_#1_clist } { ##1 }
%   }
%   {
%    \ERROR{NOT A COUNTER}
%   }
% }
%%%%
\clist_gremove_duplicates:c { g_hupfer_bound_counters_#1_clist }
% remove accidental #1 from the list
\clist_gremove_all:cn { g_hupfer_bound_counters_#1_clist } { #1 }
}

\cs_new_protected:Nn \hupfer_removeassociatedcounters:nn
{
\clist_map_inline:nn { #2 }
{
\clist_gremove_all:cn { g_hupfer_bound_counters_#1_clist } { ##1 }
}
}

\cs_new_protected:Npn \hupfer_stepcounter:n #1
{
\hupfer_orig_stepcounter:n { #1 }
\clist_if_exist:cT { g_hupfer_bound_counters_#1_clist }
{
\clist_map_inline:cn { g_hupfer_bound_counters_#1_clist }
{
\hupfer_orig_stepcounter:n { ##1 }
}
}
}

\AtBeginDocument
{
% save a copy of \stepcounter
\cs_set_eq:NN \hupfer_orig_stepcounter:n \stepcounter
% use the new one
\cs_set_eq:NN \stepcounter \hupfer_stepcounter:n
}

\ExplSyntaxOff

\newcommand{\ShowNiceCounterOutput}[5]{%
\begin{center}
\begin{tabular}{llll}
& & & \\
& \multicolumn{3}{c}{totcount page values} \\
Page & \textcolor{red}{#1} & \textcolor{blue}{#2} & \textcolor{gray}{#3} \\
\thepage & \textcolor{red}{\number\totvalue{#1}} &
\textcolor{blue}{\number\totvalue{#2}} & \textcolor{gray}{\number\totvalue{#3}} \\
& & & \\
& \multicolumn{3}{c}{totcount section values} \\
Section & \textcolor{red}{#4} & \textcolor{blue}{#5} \\
\thesection & \textcolor{red}{\number\totvalue{#4}} &
\textcolor{blue}{\number\totvalue{#5}} & \\
& & & \\
\end{tabular}
\end{center}%
}

\ExplSyntaxOn
\NewDocumentCommand{\QuickOutput}{m}
{
\prg_replicate:nn { #1 }
{
\pagenumbering{arabic}%  -> pagenumber reset to zero , on purpose inside the loop
\ShowNiceCounterOutput{page}{totalpages}{anotherpagescounter}{section}{totalsections}
\blindtext
\newpage%  some pages
}
}
\ExplSyntaxOff

\begin{document}
\regtotcounter{page} % Register a total value counter --> this will be the driver counter
\newtotcounter{totalpages}% 1st driven counter
\newtotcounter{anotherpagescounter}% 2nd driven counter
\newtotcounter{yetanotherpagescounter}% 3rd driven counter

\regtotcounter{section}
\newtotcounter{totalsections}

\DeclareAssociatedCounters{page}{totalpages,anotherpagescounter}%  Register the driver and the driven counters
\DeclareAssociatedCounters{section}{section,totalsections}%  Register the driver and the driven counters for sections%
\AddAssociatedCounters{page}{yetanotherpagescounter}%  Register the driver and the driven counters

\pagenumbering{Roman}

\chapter{The first chapter}

\section{My first section}

% Generate dummy output
\QuickOutput{10}

\section{My second section}

\QuickOutput{10}

\pagenumbering{arabic}%  -> pagenumber reset to zero

\appendix

\chapter{First Appendix chapter}

\section{My (only) appendix section}

% Generate dummy output, again ;-)
\QuickOutput{10}

% Clear the list of counters associated to page
\ClearAssociatedCountersList{page}

% Not necessary, only for removing some counters from list
\RemoveAssociatedCounters{page}{totalpages,anotherpagescounter}
% Readd the counter totalpages to the list --> in this context,
% It acts, as if it has not been removed at all
\DeclareAssociatedCounters{page}{totalpages}

\QuickOutput{20}

\end{document}


Update 2017/03/05 The assoccnt package has been superseded by xassoccnt and is available as version 1.3 on CTAN -- this package is much more powerful than assoccnt -- please don't use assoccnt any longer and switch to xassoccnt.

As egreg stated: the LaTeX kernel does not provide such a feature and perhaps there is other package doing this feature, so I have decided to provide a small package, being uploaded to CTAN and perhaps available soon.

The assoccnt.sty file

\NeedsTeXFormat{LaTeX2e}%
\ProvidesPackage{assoccnt}[2014/08/13 v0.2 -- Associate counters stepping]%
%%%
%% Copyright (2014) Dr. Christian Hupfer
%% Author: Christian Hupfer christian.hupfer@siebenfelsen.de
%%
%%
%%%%
%%% Some options later on
%%%%

\RequirePackage{etoolbox}[2011/01/03 2.2]%

\let\@@assoccnt@standardstepcounter\stepcounter%

\let\@@assoccnt@standardrefstepcounter\refstepcounter%

% Just for a quick suffix
\newcommand{\@@associatedcounterslistsuffix}{%
AssociatedCountersList%
}%

%%% Internal macro to generate the name of the list of associated counters
\newcommand{\@@assoccnt@@generatelistname}[1]{%
% #1 Name of the driver counter
@#1\@@associatedcounterslistsuffix
}%

\ifcsdef{#1}{%
\ifinlistcs{#2}{#1}{%
% Do nothing, since counter is already in the list
}{%
}%
}{%
% Nothing in here
}%
}%

% This command defines a list of counters, that should be stepped also if the driver counter
% is \stepcountered.
% A self - association is not possible, as this would lead to inconsistent counting
\newcommand{\DeclareAssociatedCounters}[2]{%
% #1 --> driver counter
% #2 --> CSV list of other counters, that should be stepped, if the driver counter is stepped
\ifcsdef{\@@assoccnt@@generatelistname{#1}}{%
%  % Nothing to be done --> List already exists
\GenericWarning{}{Warning: List of associated counters for counter #1 already exists}%
}{%
\csgdef{\@@assoccnt@@generatelistname{#1}}{}%   Define some global list
}%
% Now add the counter names from #2 to the list
% Note: Currently, it is not checked whether a counter is already added!
% Now remove an accidental self-association
\RemoveAssociatedCounter{#1}{#1}%
}%

%%% A generic macro, that removes a list entry from the list by
%%% defining a temporary list and omitting the
\newcommand{\@@assoccnt@removefromlist}[2]{%
% #1 list name
% #2 element to be removed
\ifcsdef{#1}{%
\gdef\@@mytemplist{}%
\renewcommand*{\do}[1]{%
\ifstrequal{##1}{#2}{
% Later one some success routine etc. ???
}{%
}%
}%
\dolistcsloop{#1}%
\csxdef{#1}{\@@mytemplist}%
}{%
% The list is not defined at all, can't remove something from something not existing...
}%
}%

%% Remove a particular counter from the list
\newcommand{\RemoveAssociatedCounter}[2]{%
% #1 arg: driver counter
% #2 arg: counter name to be removed
\@@assoccnt@removefromlist{\@@assoccnt@@generatelistname{#1}}{#2}%
}%

%% Remove a CSV list of counters from the list of associated counters

\newcommand{\RemoveAssociatedCounters}[2]{%
% #1 arg: driver counter
% #2 arg: CSV list of counters to be removed
\forcsvlist{\RemoveAssociatedCounter{#1}}{#2}%
}%

%% Remove all associated counters from the list
\newcommand{\ClearAssociatedCountersList}[1]{%
% #1 arg: driver counter
\csundef{\@@assoccnt@@generatelistname{#1}}%
}%

%% Test if a counter is an associated counter of driver counter
\newcommand{\IfIsAssociatedCounter}[4]{%
% #1 arg: driver counter
% #2 arg: (possibly) associated counter
% #3 arg: Code for execution on true branch
% #4 arg: Code for execution on false branch
\ifcsdef{\@@assoccnt@@generatelistname{#1}}{%
\ifinlistcs{#2}{\@@assoccnt@@generatelistname{#1}}{%
#3%
}{%
#4%
}%
}{% List does not exist, so it's not associated
#4%
}%
}%

%%% The stepcounter wrapper for the standard stepcounter command
%%% This is just for convenience, if the command as to be improved/extended later on in future
\newcommand{\@@assoccnt@stepcounter}[1]{%
\@@assoccnt@standardstepcounter{#1}%
}%

%%% Not needed so far
\newcommand{\@@assoccnt@refstepcounter}[1]{%
\@@assoccnt@standardrefstepcounter{#1}%
}%

%%%% Redefinition of the \stepcounter command
\renewcommand{\stepcounter}[1]{%
\@@assoccnt@standardstepcounter{#1}%   Traditional behaviour, since this is expected!
\ifcsdef{\@@assoccnt@@generatelistname{#1}}{%    Check first, whether the list exists at all,
\forlistcsloop{\@@assoccnt@stepcounter}{\@@assoccnt@@generatelistname{#1}}%  March through the list
}{%
%No list -> do nothing at all
}%
}%

%%%% Redefinition of the \stepcounter command
\renewcommand{\refstepcounter}[1]{%
\@@assoccnt@standardstepcounter{#1}%   Traditional behaviour, since this is expected!
\protected@edef\@currentlabel%
{\csname p@#1\endcsname\csname the#1\endcsname}%
\ifcsdef{\@@assoccnt@@generatelistname{#1}}{%    Check first, whether the list exists at all,
\forlistcsloop{\@@assoccnt@stepcounter}{\@@assoccnt@@generatelistname{#1}}%  March through the list
}{%
%No list -> do nothing at all
}%
}%

\endinput%


And some test driver file

\documentclass{book}%

\usepackage{totcount}%
\usepackage{assoccnt}%

% Some packages only for output and dummy pages
\usepackage{blindtext}%
\usepackage{forloop}%
\usepackage{xcolor}%

\newcounter{loopcounter}%

\newcommand{\ShowNiceCounterOutput}[5]{%
\begin{center}%
\begin{tabular}{llll}%
& & & \tabularnewline
& \multicolumn{3}{c}{totcount page values} \tabularnewline
Page & \textcolor{red}{#1} & \textcolor{blue}{#2} & \textcolor{gray}{#3} \tabularnewline
\thepage & \textcolor{red}{\number\totvalue{#1}} & \textcolor{blue}{\number\totvalue{#2}} & \textcolor{gray}{\number\totvalue{#3}} \tabularnewline
& & & \tabularnewline
& \multicolumn{3}{c}{totcount section values} \tabularnewline
Section & \textcolor{red}{#4} & \textcolor{blue}{#5} \tabularnewline
\thesection & \textcolor{red}{\number\totvalue{#4}} & \textcolor{blue}{\number\totvalue{#5}} & \tabularnewline
&  & & \tabularnewline
\end{tabular}
\end{center}%
}%

\newcommand{\QuickOutput}[1]{%
%
\forloop{loopcounter}{1}{\value{loopcounter} < \numexpr #1+1}{%
\pagenumbering{arabic}%  -> pagenumber reset to zero , on purpose inside the loop
\ShowNiceCounterOutput{page}{totalpages}{anotherpagescounter}{section}{totalsections}%
\blindtext%
\newpage%  some pages
}%
}%

\begin{document}
\regtotcounter{page} % Register a total value counter --> this will be the driver counter
\newtotcounter{totalpages}% 1st driven counter
\newtotcounter{anotherpagescounter}% 2nd driven counter
\newtotcounter{yetanotherpagescounter}% 3rd driven counter

\regtotcounter{section}%
\newtotcounter{totalsections}

\DeclareAssociatedCounters{page}{totalpages,anotherpagescounter}%  Register the driver and the driven counters
\DeclareAssociatedCounters{section}{section,totalsections}%  Register the driver and the driven counters for sections%
\DeclareAssociatedCounters{page}{yetanotherpagescounter}%  Register the driver and the driven counters

\pagenumbering{Roman}%

\chapter{The first chapter}%

\section{My first section}%

% Generate dummy output
\QuickOutput{10}%

\section{My second section}%

\QuickOutput{10}%

\pagenumbering{arabic}%  -> pagenumber reset to zero

\appendix

\chapter{First Appendix chapter}%

\section{My (only) appendix section}%

% Generate dummy output, again ;-)
\QuickOutput{10}%

% Clear the list of counters associated to page
\ClearAssociatedCountersList{page}%

% Not necessary, only for removing some counters from list
\RemoveAssociatedCounters{page}{totalpages,anotherpagescounter}%
% Readd the counter totalpages to the list --> in this context,
% It acts, as if it has not been removed at all
\DeclareAssociatedCounters{page}{totalpages}

\QuickOutput{20}%

\end{document}


The output of the driver is basically the same as in the snapshot of the question.

Notes

• There is no test so far whether the arguments of the several commands are counter (names) at all
• There is no test that two (or more) associated counters are not mutally associated counters.

I will provide updates and improvements continously.

• Too many useless % for my tastes. ;-) – egreg Aug 12 '14 at 9:22
• @egreg: As long they do no harm ... I rather use one % too much as too few of them ;-) – user31729 Aug 12 '14 at 10:27
• Well, you're missing two of them. I leave to you as an exercise to find which ones. Hint: \listgadd. – egreg Aug 12 '14 at 10:29
• @egreg: I found at least one of them, when I was just looking into it ;-) – user31729 Aug 12 '14 at 10:30