7

I’m defining datas in LaTeX with a custom command that stores both a label and a number:

\documentclass{article}
\usepackage{parskip}

\newcommand{\data}[3]{%
  \expandafter\newcommand\csname #1\endcsname[1][]{#2##1}%
  \expandafter\newcommand\csname #1Number\endcsname{#3}%
}

\newcommand{\getnumber}[1]{%
  \mbox{\textbf{(\csname #1Number\endcsname)}}%
}

\begin{document}

\data{tempA}{aaa}{1}
\data{tempB}{bbb}{2}
\data{tempC}{ccc}{3}
\data{tempD}{ddd}{4}
\data{tempE}{eee}{5}
\data{tempF}{fff}{6}

Data: \tempA \newline
Number: \getnumber{tempA}

\end{document}

For a single compound, \getnumber{tempA} outputs (1).

Now I want to create a command that takes a list of compound names and outputs a range summary of their numbers. For example:

\getnumberrange{tempA,tempB,tempC,tempF}  % should produce (1-3,6)

I’m not sure how to iterate over a list of command names, retrieve their numbers, and compress consecutive numbers into ranges. How can I implement this in LaTeX?

4
  • 1
    You could consider to store them into a l3clist, that is(is automatically rearrange the order) \getnumberrange{tempA,tempC,tempB,tempF} to a clist: 1,3,2,6, and be sorted 1,2,3,6, and use loops to judge whether the next item(clist[i+1]) of clist[i] is clist[i]+1, and replace them with -. But I was not sure what is the exact use of this command, it looks like duplicated wheels in cleveref or zref-clever or cite package.... Commented 2 days ago
  • 1
    is there a reason not to use latex's standard \label/\ref system here? as @Explorer said, then you could use e.g. zref-clever which can compress and format ranges etc. in addition to this, I think it would help if you could say a bit more about the end goal here. Commented 2 days ago
  • This quesion is the opposite/dual one of this: tex.stackexchange.com/q/494712/322482 Commented 2 days ago
  • Mandatory read for this: lambda-lists (documentation of lambda.sty) Commented yesterday

3 Answers 3

5

Based on my comment, I give an expl3 attempt:

\documentclass{article}
\newcommand{\data}[3]{%
  \expandafter\newcommand\csname#1\endcsname[1][]{#2##1}%
  \expandafter\newcommand\csname#1Number\endcsname{#3}%
}
\ExplSyntaxOn
\cs_new:Npn \__taiwan_getnum:n #1
{
    \cs:w#1Number\cs_end:
}
\newcommand{\getnumber}[1]{%
%   \mbox{\textbf{(\__taiwan_getnum:n {#1})}}%
    \__taiwan_getnum:n {#1}%
}
\clist_new:N \l__taiwan_numrange_clist
\clist_new:N \l__taiwan_num_clist
\tl_new:N \l__taiwan_tmp_tl
\int_new:N \l__taiwan_tmp_int
\int_new:N \l__taiwan_start_int
\cs_new:Npn \__taiwan_getnumrange:n #1
{
    \group_begin:
    \clist_set:Nn \l__taiwan_numrange_clist {#1}
    \clist_clear:N \l__taiwan_num_clist
    \clist_map_inline:Nn \l__taiwan_numrange_clist
    {
        \clist_put_right:Nn \l__taiwan_num_clist {\__taiwan_getnum:n {##1}}%
    }
    \clist_sort:Nn \l__taiwan_num_clist
    {
        \int_compare:nNnTF { ##1 } > { ##2 }
        { \sort_return_swapped: }
        { \sort_return_same: }
    }
    \clist_pop:NN \l__taiwan_num_clist \l__taiwan_tmp_tl
    \int_set:Nn \l__taiwan_tmp_int { \l__taiwan_tmp_tl }
    \int_set:Nn \l__taiwan_start_int { \l__taiwan_tmp_tl }
    \clist_map_inline:Nn \l__taiwan_num_clist
    {
        \int_compare:nNnTF { ##1 } = { \int_eval:n { \l__taiwan_tmp_int + 1 } }
        {
            % consecutive
            \int_set:Nn \l__taiwan_tmp_int { ##1 }
        }
        {
            % not consecutive
            \int_compare:nNnTF { \l__taiwan_start_int } = { \l__taiwan_tmp_int }
            {
                % single number
                ~\int_use:N \l__taiwan_start_int ,
            }
            {
                % range
                ~ \int_use:N \l__taiwan_start_int -- \int_use:N \l__taiwan_tmp_int ,
            }
            \int_set:Nn \l__taiwan_start_int { ##1 }
            \int_set:Nn \l__taiwan_tmp_int { ##1 }
        }
    }
    \int_compare:nNnTF { \l__taiwan_start_int } = { \l__taiwan_tmp_int }
    {
        ~\int_use:N \l__taiwan_start_int
    }
    {
        ~ \int_use:N \l__taiwan_start_int -- \int_use:N \l__taiwan_tmp_int
    }
    \group_end:
}
\newcommand{\getnumberrange}[1]{%
    \__taiwan_getnumrange:n {#1}
}
\ExplSyntaxOff

\begin{document}

\data{tempA}{aaa}{1}
\data{tempB}{bbb}{2}
\data{tempC}{ccc}{3}
\data{tempD}{ddd}{4}
\data{tempE}{eee}{5}
\data{tempF}{fff}{6}

Data: \tempA 

Number: \getnumber{tempA} and \getnumber{tempD} 

Number Range: \getnumberrange{tempA,tempE,tempF,tempC}

Number Range: \getnumberrange{tempA,tempC,tempE}

Number Range: \getnumberrange{tempD,tempC}

Number Range: \getnumberrange{tempA,tempE,tempF,tempC,tempB,tempD}

Number Range: \getnumberrange{tempA,tempE,tempF,tempB,tempD}

\end{document}

result

Noted that I don't think I did a good job, as cfr also mentioned at here, there are similar achievement in zref-clever or cite to privide similar option named sort&compress, the above is probably reinvent-ing the wheels(NO!), so just regard it as my latex3 exercise. As for the () outside the outcome, you can add it on your own.

5

Here's my version with a few comments on the code.

I always avoid defining commands: it's better to have a separate namespace for such jobs.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\setdata}{mmm}
 {
  \prop_gput:Nnn \g_taiwan_data_prop {#1@name} {#2}
  \prop_gput:Nnn \g_taiwan_data_prop {#1@number} {#3}
 }
\NewExpandableDocumentCommand{\getname}{m}
 {
  \taiwan_data_name:n {#1}
 }
\NewDocumentCommand{\getdata}{m}
 {
  \taiwan_data_get:n {#1}
 }

\prop_new:N \g_taiwan_data_prop
\seq_new:N \l__taiwan_data_items_seq
\seq_new:N \l__taiwan_data_ranges_seq
\seq_new:N \l__taiwan_data_ranges_final_seq
\tl_new:N \l__taiwan_data_collect_tl
\int_new:N \l__taiwan_data_previous_int

\cs_new:Nn \taiwan_data_name:n
 {
  \prop_item:Nn \g_taiwan_data_prop {#1@name}
 }

\cs_new_protected:Nn \taiwan_data_get:n
 {
  (
  \int_compare:nTF { \clist_count:n {#1} = 1 }
   {% if just one item, produce it
    \prop_item:Nn \g_taiwan_data_prop {#1@number}
   }
   {% otherwise, sort and print
    \__taiwan_data_sort:n {#1}
   }
  )
 }

\cs_new_protected:Nn \__taiwan_data_sort:n
 {
  % first phase: collect the numbers
  \seq_clear:N \l__taiwan_data_items_seq
  \clist_map_inline:nn {#1}
   {
    \seq_put_right:Ne \l__taiwan_data_items_seq { \prop_item:Nn \g_taiwan_data_prop {##1@number} }
   }
  % second phase: sort the sequence
  \seq_sort:Nn \l__taiwan_data_items_seq
   {
    \int_compare:nNnTF {##1}>{##2}
     { \sort_return_swapped: }
     { \sort_return_same: }
   }
  % third phase: print
  % 1. detach the first item
  \seq_pop_left:NN \l__taiwan_data_items_seq \l__taiwan_data_collect_tl
  \int_set:Nn \l__taiwan_data_previous_int { \l__taiwan_data_collect_tl }
  % 2. start the mapping
  \seq_map_inline:Nn \l__taiwan_data_items_seq
   {
    \int_compare:nTF { ##1 == \l__taiwan_data_previous_int + 1 }
     {% we're in a range
      \tl_put_right:Nn \l__taiwan_data_collect_tl { , }
     }
     {% not in a range
      \tl_put_right:Nn \l__taiwan_data_collect_tl { // }
     }
    \tl_put_right:Nn \l__taiwan_data_collect_tl {##1}
    \int_set:Nn \l__taiwan_data_previous_int {##1}
   }
  % 3. split at //
  \seq_set_split:NnV \l__taiwan_data_ranges_seq { // } \l__taiwan_data_collect_tl
  % 4. massage the possible ranges
  \seq_clear:N \l__taiwan_data_ranges_final_seq
  \seq_map_inline:Nn \l__taiwan_data_ranges_seq
   {
    \int_compare:nTF { \clist_count:n {##1} > 1 }
     {% we have a range
      \seq_put_right:Ne \l__taiwan_data_ranges_final_seq
       { \clist_item:nn {##1} {1} -- \clist_item:nn {##1} {-1} }
     }
     {% no range
      \seq_put_right:Nn \l__taiwan_data_ranges_final_seq {##1}
     }
   }
  % 5. print
  \seq_use:Nn \l__taiwan_data_ranges_final_seq {,~}
 } 

\ExplSyntaxOff

\begin{document}

\setdata{tempA}{aaa}{1}
\setdata{tempB}{bbb}{2}
\setdata{tempC}{ccc}{3}
\setdata{tempD}{ddd}{4}
\setdata{tempE}{eee}{5}
\setdata{tempF}{fff}{6}


Data: \getname{tempA} -- Number: \getdata{tempA}

\getdata{tempA,tempB,tempC,tempF}

\getdata{tempA,tempB,tempC,tempE,tempF}

\getdata{tempB,tempF,tempE,tempA,tempC} % same as before

\end{document}

output

2

Just because it's possible, the following defines a fully expandable version that retrieves the numeric values, then sorts them, and finally turns them into a comma separated list with ranges detected and replaced by <start>--<end>. If <end> is only bigger than <start> by 1 it'll not be considered a range.

\documentclass{article}

\ExplSyntaxOn
\NewDocumentCommand \data { m m m }
  {
    \ExpandArgs{c} \NewDocumentCommand {#1} { O{} } { #2 ##1 }
    \cs_new:cpx { taiwan~number-\tl_to_str:n {#1} } { \int_eval:n {#3} }
  }
\NewExpandableDocumentCommand \getnumber { m }
  { ( \exp_after:wN \use:n \__taiwan_getnumber:n {#1} ) }
\NewExpandableDocumentCommand \getnumberrange { m }
  { ( \taiwan_getnumber_range:n {#1} ) }

% Turn a clist of data labels into a simplified clist of numbers possibly
% containing ranges.
\cs_new:Npn \taiwan_getnumber_range:n #1
  {
    % #1: clist of data labels
    \exp_last_unbraced:Ne \use_none:n % remove leading ,
      {
        \taiwan_rangify:e
          {
            \tl_sort:eN
              { \clist_map_function:nN {#1} \__taiwan_getnumber:n }
              \__taiwan_if_smaller_than:nnTF
          }
      }
  }
\cs_generate_variant:Nn \tl_sort:nN { e }
\cs_new:Npn \__taiwan_if_smaller_than:nnTF #1 { \int_compare:nNnTF {#1} < }
% Turn a single data label into a number inside of braces (so into an item of a
% tl).
\cs_new:Npn \__taiwan_getnumber:n #1
  {
    {
      \cs_if_exist_use:cF { taiwan~number-\tl_to_str:n {#1} }
        { \msg_expandable_error:nnn { taiwan } { unknown-number } {#1} 0 }
    }
  }

% Simplify a tl of numbers into a clist possibly containing ranges.
\cs_new:Npn \taiwan_rangify:n #1
  {
    % #1: tl with each item being a number
    % we use \q_nil to denote the end of the list and \q_stop as a marker to
    % gobble the remainder of the current loop iteration inside
    % \__taiwan_rangify:w.
    \__taiwan_rangify:w #1 \q_nil \q_stop
  }
\cs_generate_variant:Nn \taiwan_rangify:n { e }
\cs_new:Npn \__taiwan_rangify:w #1
  {
    % #1: either \q_nil if the tl provided to \taiwan_rangify:n was blank or the
    %     first number
    % if #1 is \q_nil we need to leave a single token for the \use_none:n in
    % \taiwan_getnumber_range:n.
    \use_none_delimit_by_q_nil:w #1 , \use_none_delimit_by_q_stop:w \q_nil
    \__taiwan_rangify_aux:nw {#1}
  }
\cs_new:Npn \__taiwan_rangify_aux:nw #1 #2
  {
    % #1: a number
    % #2: either \q_nil or the next number from the list >= #1
    \use_none_delimit_by_q_nil:w #2 ,#1 \use_none_delimit_by_q_stop:w \q_nil
    \int_case:nnF {#2}
      {
        { #1 }     { \__taiwan_rangify_aux:nw {#1} }
        { #1 + 1 } { \__taiwan_rangify_aux:Nnnw \use_i:nn {#1} {#2} }
      }
      { , #1 \__taiwan_rangify_aux:nw {#2} }
  }
\cs_new:Npn \__taiwan_rangify_aux:Nnnw #1#2#3 #4
  {
    % #1: \use_i:nn if #2 + 1 = #3, \use_ii:nn if #2 + 1 < #3
    % #2: a number
    % #3: a number bigger than #2
    % #4: either \q_nil or the next number from the list >= #3
    \use_none_delimit_by_q_nil:w #4
      \__taiwan_rangify_range:Nnn #1 {#2} {#3}
      \use_none_delimit_by_q_stop:w
      \q_nil
    \int_case:nnF {#4}
      {
        { #3 }     { \__taiwan_rangify_aux:Nnnw #1 {#2} {#3} }
        { #3 + 1 } { \__taiwan_rangify_aux:Nnnw \use_ii:nn {#2} {#4} }
      }
      {
        \__taiwan_rangify_range:Nnn #1 {#2} {#3}
        \__taiwan_rangify_aux:nw {#4}
      }
  }
% output two numbers either as a range (if #2 + 1 < #3) or as a list
\cs_new:Npn \__taiwan_rangify_range:Nnn #1#2#3
  {
    % #1: \use_i:nn if #2 + 1 = #3, \use_ii:nn if #2 + 1 < #3
    % #2: a number
    % #3: a number bigger than #2
    , #2 #1 { , } { -- } #3
  }
\msg_new:nnn { taiwan } { unknown-number }
  { Unknown~ number~ #1.~ Using~ 0~ instead. }
\ExplSyntaxOff

\begin{document}
\data{tempA}{aaa}{1}
\data{tempB}{bbb}{2}
\data{tempC}{ccc}{3}
\data{tempD}{ddd}{4}
\data{tempE}{eee}{5}
\data{tempF}{fff}{6}

\tempA: \getnumber{tempA}

\tempA: \getnumberrange{tempA}

\getnumberrange{tempA,tempF,tempE,tempB,tempC,tempA,tempA}

% proof things are expandable
\edef\foo{\getnumberrange{tempA,tempF,tempE,tempB,tempC,tempA,tempA}}
\texttt{\meaning\foo}
\end{document}

enter image description here

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.