2

I read a clist and create a prop-list which should behave as a hash in which the value for a given key is the number of occurences of this value in the clist. Below my code which, so far, does work as expected.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[a4paper]{geometry}
\usepackage{lmodern}
\usepackage[log-declarations=false]{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\Accumulate}
{ m }
{
  \clist_clear_new:N \l_tmpa_clist
  \clist_set:Nn \l_tmpa_clist {#1}

  \prop_clear_new:N \l_tmpa_prop

  \tl_clear:N \l_tmpa_tl
  \tl_clear:N \l_tmpb_tl

  \bool_set_true:N  \l_tmpa_bool % the clist is not void

  \bool_do_while:Nn \l_tmpa_bool
  {
    \par 
    \clist_pop:NNTF \l_tmpa_clist \l_tmpa_tl
    {
      "l_tmpa_tl"~ \tl_use:N \l_tmpa_tl \quad{}
      \prop_if_in:NVTF \l_tmpa_prop \l_tmpa_tl
      {
        % l_tmpa is a key in hash l_tmpa_prop
        % we get the associated value
        \prop_get:NVN \l_tmpa_prop \l_tmpa_tl \l_tmpb_tl
        \tl_show:N \l_tmpb_tl 
        "l_tmpb_tl"~ \tl_use:N \l_tmpb_tl
        % we want it as a number
        \int_set:Nn \l_tmpb_int { \l_tmpb_tl }
        \int_show:N \l_tmpb_int
        \par "l_tmpb_int"~ \int_to_arabic:n { \l_tmpb_tl }
        \int_gincr:N \l_tmpb_int
        % after that the preceding value should be incremented
        \prop_gput:NVn \l_tmpa_prop \l_tmpa_tl { \l_tmpb_int }
      }
      {
        % l_tmpa is not yet a key in hash l_tmpa_prop
        \prop_gput:NVn \l_tmpa_prop \l_tmpa_tl { 1 }
      }
      \prop_show:N \l_tmpa_prop
    }
    {
      \bool_set_false:N \l_tmpa_bool
    }
  }
}

\ExplSyntaxOff


\begin{document}

\Accumulate{1, 2, 3}

\par

\Accumulate{1, 2, 2}

\end{document}

As the log shows, it doesn't work as expected when a key occurs more than once:

The property list \l_tmpa_prop contains the pairs (without outer braces):
>  {1}  =>  {1}.
<recently read> }

l.63 \Accumulate{1, 2, 3}


The property list \l_tmpa_prop contains the pairs (without outer braces):
>  {1}  =>  {1}
>  {2}  =>  {1}.
<recently read> }

l.63 \Accumulate{1, 2, 3}


The property list \l_tmpa_prop contains the pairs (without outer braces):
>  {1}  =>  {1}
>  {2}  =>  {1}
>  {3}  =>  {1}.
<recently read> }

l.63 \Accumulate{1, 2, 3}


The property list \l_tmpa_prop contains the pairs (without outer braces):
>  {1}  =>  {1}.
<recently read> }

l.67 \Accumulate{1, 2, 2}


The property list \l_tmpa_prop contains the pairs (without outer braces):
>  {1}  =>  {1}
>  {2}  =>  {1}.
<recently read> }

l.67 \Accumulate{1, 2, 2}


> \l_tmpb_tl=1.
<recently read> }

l.67 \Accumulate{1, 2, 2}


> \l_tmpb_int=1.
<recently read> }

l.67 \Accumulate{1, 2, 2}

Thanks for reading, any help will be greatly appreciated

The property list \l_tmpa_prop contains the pairs (without outer braces):
>  {1}  =>  {1}
>  {2}  =>  {\l_tmpb_int }.
<recently read> }

l.67 \Accumulate{1, 2, 2}
2
  • 1
    Bonjour chez TeX.SX!
    – AlexG
    Feb 24, 2017 at 12:17
  • In your original code, replace one \prop_gput:NVn by \prop_gput:NVV (to store the valur of its third argument \l_tmpa_int). Feb 24, 2017 at 16:53

2 Answers 2

4

Easier (and shorter in my point of view)

Use seq variable, populate it with the #1 argument and add the keys value in a \seq_map_inline variable, branching with \prop_if_in.

There is no need of pop and push etc or fiddling with tl variables, in my point of view.

If the values should exist outside, use a global prop variable.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[a4paper]{geometry}
\usepackage{lmodern}
\usepackage[log-declarations=false]{xparse}

\ExplSyntaxOn

\cs_generate_variant:Nn \int_set:Nn {Nx}

\NewDocumentCommand{\Accu}{m}{%
  \prop_clear:N \l_tmpa_prop
  \seq_set_from_clist:Nn \l_tmpa_seq {#1}%
  \seq_map_inline:Nn \l_tmpa_seq {%
    \prop_if_in:NnTF \l_tmpa_prop {##1} {%
      \int_set:Nx \l_tmpa_int {\prop_item:Nn {\l_tmpa_prop} {##1}}
      \int_incr:N \l_tmpa_int 
      \prop_put:Nnx \l_tmpa_prop {##1} {\int_use:N \l_tmpa_int }
    }{%
      \prop_put:Nnn \l_tmpa_prop {##1} {1}
    }
  }
  \prop_map_inline:Nn \l_tmpa_prop {%
    ##1~ ##2\par
  }
}


\ExplSyntaxOff


\begin{document}

\Accu{1,2,3}

\Accu{1,2,2,3,3,3,4,1,4}

\end{document}

enter image description here

4
  • As can be seen from the screen shot, the counting is correct. The % are just for organizing, not for spurious spaces
    – user31729
    Feb 24, 2017 at 12:27
  • \prop_item:nn is inevitably not very fast. It is faster to use the non-expandable functions \prop_get etc. Similarly \prop_if_in is slow. Feb 24, 2017 at 16:54
  • @BrunoLeFloch: I know, but it is expandable, contrary to \prop_get. And I like \prop_item very much ;-) (And egreg does use both \prop_if_in and \prop_item as well...)
    – user31729
    Feb 24, 2017 at 16:55
  • Here you don't need expandability. Actually, speedwise the best would be to use \prop_get:NnNTF for the branching so that in the "true" branch you directly have the item itself. I didn't look yet at egreg's answer. Feb 24, 2017 at 17:02
4

Since you know the value of each key is an integer, you can use it in \int_eval:n through \prop_item:Nn, which is expandable.

I propose an interface where you can accumulate in different property lists and also choose whether clearing the property list or not (with the *-variant).

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\Accumulate}{sO{standard}m}
 {
  % some administrative tasks
  \IfBooleanTF{#1}
   {% no *: clear the property list, creating it if necessary
    \prop_clear_new:c { l_tds_accumulate_#2_prop }
   }
   {% * variant: don't clear the property list, creating it if necessary
    \prop_if_exist:cF { l_tds_accumulate_#2_prop }
     {
      \prop_new:c { l_tds_accumulate_#2_prop }
     }
   }
  % the main function
  \tds_accumulate:nn { #2 } { #3 }
 }

\cs_new_protected:Nn \tds_accumulate:nn
 {
  % do a mapping on the input
  \clist_map_inline:nn { #2 }
   {
    \tds_accumulate_check:nn { #1 } { ##1 }
   }
 }

\cs_new_protected:Nn \tds_accumulate_check:nn
 {
  \prop_if_in:cnTF { l_tds_accumulate_#1_prop } { #2 }
   {% the key is already present: increment its value
    \prop_put:cnx { l_tds_accumulate_#1_prop } { #2 }
     { \int_eval:n { \prop_item:cn { l_tds_accumulate_#1_prop }{#2} + 1 } }
   }
   {% the key is new: put in the value 1
    \prop_put:cnn { l_tds_accumulate_#1_prop } { #2 } { 1 }
   }
 }

\NewDocumentCommand{\showaccumulate}{O{standard}}
 {
  \prop_show:c { l_tds_accumulate_#1_prop }
 }
\ExplSyntaxOff

\Accumulate{1, 2, 3}
\showaccumulate

\Accumulate{1, 2, 2}
\showaccumulate

\Accumulate*{1,2,1,2,1,2,3}
\showaccumulate

\Accumulate[new]{ab,ac,ab,abc}
\showaccumulate[new]

\stop

Here's the result on the console.

The property list \l_tds_accumulate_standard_prop contains the pairs (without
outer braces):
>  {1}  =>  {1}
>  {2}  =>  {1}
>  {3}  =>  {1}.
<recently read> }

l.50 

? 
The property list \l_tds_accumulate_standard_prop contains the pairs (without
outer braces):
>  {1}  =>  {1}
>  {2}  =>  {2}.
<recently read> }

l.53 

? 
The property list \l_tds_accumulate_standard_prop contains the pairs (without
outer braces):
>  {1}  =>  {4}
>  {2}  =>  {5}
>  {3}  =>  {1}.
<recently read> }

l.56 

? 
The property list \l_tds_accumulate_new_prop contains the pairs (without outer
braces):
>  {ab}  =>  {2}
>  {ac}  =>  {1}
>  {abc}  =>  {1}.
<recently read> }

l.58 \showaccumulate[new]

? 

As Bruno LeFloch remarks in comments, \prop_item:Nn is slower than \prop_get:NnN; here's a version using the latter.

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\Accumulate}{sO{standard}m}
 {
  % some administrative tasks
  \IfBooleanTF{#1}
   {% no *: clear the property list, creating it if necessary
    \prop_clear_new:c { l_tds_accumulate_#2_prop }
   }
   {% * variant: don't clear the property list, creating it if necessary
    \prop_if_exist:cF { l_tds_accumulate_#2_prop }
     {
      \prop_new:c { l_tds_accumulate_#2_prop }
     }
   }
  % the main function
  \tds_accumulate:nn { #2 } { #3 }
 }

\cs_new_protected:Nn \tds_accumulate:nn
 {
  % do a mapping on the input
  \clist_map_inline:nn { #2 }
   {
    \tds_accumulate_check:nn { #1 } { ##1 }
   }
 }

\cs_new_protected:Nn \tds_accumulate_check:nn
 {% check whether the key already exists
  \prop_get:cnNTF { l_tds_accumulate_#1_prop } { #2 } \l__tds_accumulate_temp_tl
   {% the key already exists: increment its value
    \prop_put:cnx { l_tds_accumulate_#1_prop } { #2 }
     { \int_eval:n { \l__tds_accumulate_temp_tl + 1 } }
   }
   {% the key doesn't exist: put 1
    \prop_put:cnn { l_tds_accumulate_#1_prop } { #2 } { 1 }
   }
 }
\cs_generate_variant:Nn \prop_get:NnNTF { c }

\NewDocumentCommand{\showaccumulate}{O{standard}}
 {
  \prop_show:c { l_tds_accumulate_#1_prop }
 }
\ExplSyntaxOff

\Accumulate{1, 2, 3}
\showaccumulate

\Accumulate{1, 2, 2}
\showaccumulate

\Accumulate*{1,2,1,2,1,2,3}
\showaccumulate

\Accumulate[new]{ab,ac,ab,abc}
\showaccumulate[new]

\stop
2
  • See my comments on the other answer, \prop_item:nn is nice because of expandability but it is slower than non-expandable functions. Feb 24, 2017 at 17:03
  • @BrunoLeFloch Yes, I know it's slow. I'll add a faster version with \prop_get:NnN
    – egreg
    Feb 24, 2017 at 17:12

You must log in to answer this question.

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