3

I try to learn expl3. I try to create a property list. The values should be numbered sequentially, but somehow I always save only the last value. I would expect as output:

Test [1][2][3][2][1] (One,1) (Two,2) (Three,3)

but I get

[1][2][3][3][3] (One,3) (Two,3) (Three,3)

I would be happy if someone can help me.

Furthermore, how can I replace my \ifstringequal cmd with standard \str_if_eq:NFT? I tried, but I always get errors. Seems like I didn't get the syntax yet...

What is in your opinion the best way to learn expl3?

\documentclass{standalone}
\usepackage{tikz} 

\makeatletter
\newcommand{\ifstringequal}[4]{%
    \ifnum\pdf@strcmp{#1}{#2}=\z@
        #3%
    \else
        #4%
    \fi
}
\makeatother
\begin{document}
\ExplSyntaxOn
    \prop_new:N \g_my_prop
    \bool_new:N \l_my_flag
    \int_new:N \l_citecounter_int
    \int_set:Nn \l_citecounter_int {0}
    \newcommand{\zzzz}[2]{%
        \bool_set_false:N \l_my_flag
        \prop_map_inline:Nn \g_my_prop {
            \ifstringequal{#2}{##1}{
                \bool_set_true:N \l_my_flag%
            }{%
                %donothing
            }
        }
        \bool_if:nTF {\l_my_flag}{% Value already exists in dictionary
            $\lbrack$\exp_args:Nno\prop_item:Nn \g_my_prop{#2}$\rbrack$
        }{%Value didnt exists in dictionary
            \int_incr:N \l_citecounter_int % Increment the counter
            $\lbrack$\int_use:N \l_citecounter_int$\rbrack$
            \exp_args:Nno\prop_put:Nnn \g_my_prop {#2}{\int_use:N \l_citecounter_int} %\int_use:N
        }
    }
\ExplSyntaxOff

Test:\\
\zzzz{opt}{One}
\zzzz{opt}{Two}
\zzzz{opt}{Three}
\zzzz{opt}{Two}
\zzzz{opt}{One}
\par\vspace{2ex}
\ExplSyntaxOn \prop_map_inline:Nn \g_my_prop {
    (#1, #2)
} 
\ExplSyntaxOff

\end{document}
3
  • 1
    Why are you loading tikz? Does the order matter here? Because a property list is unordered. If the order matters, you probably want a sequence or a comma-separated list. Your variables should all include <module>_<description> in their names, where the <module> is used as a common prefix.
    – cfr
    Commented Jun 21 at 0:35
  • \ifstringequal could be replaced by \str_if_eq:NNTF or \str_if_eq:nnTF. Also \NewDocumentCommand should be preferred over \newcommand for macros which need to be protected. Commented Jun 21 at 1:03
  • I've slightly edited some of your formatting and punctuation to try to make the question clearer. If you don't like the result, do feel free to roll the changes back.
    – cfr
    Commented Jun 21 at 2:52

2 Answers 2

5

I propose a different approach using a sequence. Unlike a property list, which is unordered, sequences can be used as indexed sequences. In this case, the data structure is inherently ordered and every item added to the sequence has a particular place in that (linear) ordering, which we can use directly rather than keeping track ourselves with a separate count. Moreover, we don't need the boolean because (whether we use a property list or a sequence), expl3 already provides us with all the conditional tests we need.

In your test input, the first argument is always opt, so this argument is not doing any work. If you actually need multiple lists of indexed items, you could use a first argument to specify the relevant sequence or, if it doesn't exist, to create it on-the-fly.

Simpler case

To implement the simpler case, where you only really need a single sequence of items, for example,

\documentclass{standalone}
% ateb: https://tex.stackexchange.com/a/721021/
\begin{document}
\ExplSyntaxOn
\seq_new:N \g_viggo_cites_seq
\newcommand{\zzzz}[1]{
  \seq_if_in:NnTF \g_viggo_cites_seq { #1 }
  {
    \seq_map_indexed_inline:Nn \g_viggo_cites_seq
    {
      \str_if_eq:nnT {#1}{##2}
      {
        [ \int_to_arabic:n { ##1 } ]
        \seq_map_break:
      }
    }
  }{
    \seq_gput_right:Nn \g_viggo_cites_seq { #1 }
    [ \int_to_arabic:n { \seq_count:N \g_viggo_cites_seq } ]
  }
}
\ExplSyntaxOff

Test:\par
\zzzz{One}
\zzzz{Two}
\zzzz{Three}
\zzzz{Two}
\zzzz{One}
\par\vspace{2ex}
\ExplSyntaxOn \seq_map_indexed_inline:Nn \g_viggo_cites_seq {
  (#2, #1)
} 
\ExplSyntaxOff
  
\end{document}

output from indexed sequence

Fancier version

We define \zzz to take an optional argument. By default, it uses a sequence based on citations. If an optional argument is given, it bases the sequences on that instead.

Then we define \zzzout to take an optional star followed by an ordinary optional argument. The latter determines the sequence name as for \zzz. The former determines whether the items are combined before being wrapped in parentheses (starred) or not (unstarred).

We need a scratch sequence in case the citations are combined in \zzzout.

\seq_new:N \l__viggo_tmpa_seq

Define \zzz as a simple wrapper around an internal function.

\NewDocumentCommand \zzz { O {citations} m }
{
  \group_begin:
    \viggo_zzz:nn { #1 } { #2 }
  \group_end:
}

\viggo_zzz:nn actually does the work of creating a sequence, if necessary, adding the item etc. This also allows us to pass a comma-separated list of items to \zzz rather than adding them one-by-one.

\cs_new_protected:Nn \viggo_zzz:nn
{
  \seq_if_exist:cF { g_viggo_#1_seq }
  {
    \seq_new:c { g_viggo_#1_seq }
  }
  \clist_map_inline:nn { #2 }
  {
    \seq_if_in:cnTF  { g_viggo_#1_seq } { ##1 }
    {
      \seq_map_indexed_inline:cn  { g_viggo_#1_seq }
      {
        \str_if_eq:nnT {##1}{####2}
        {
          [ \int_to_arabic:n { ####1 } ]
          \seq_map_break:
        }
      }
    }{
      \seq_gput_right:cn  { g_viggo_#1_seq } { ##1 }
      [ \int_to_arabic:n { \seq_count:c  { g_viggo_#1_seq } } ]
    }    
  }
}

Define \zzzout. Should probably be just a wrapper, but I didn't bother here.

\NewDocumentCommand \zzzout { s O {citations} }
{
  \group_begin:
  \IfBooleanTF { #1 }
  {

We shouldn't really need to clear the sequence, but it can't hurt.

    \seq_clear:N \l__viggo_tmpa_seq

We map over the items stored in the sequence as before, but rather than typesetting them, we add our comma-separated pairs to our scratch sequence.

    \seq_map_indexed_inline:cn {g_viggo_#2_seq} {
      \seq_put_right:Nn \l__viggo_tmpa_seq
      {
        ##2, ##1
      }
    }

Now we typeset the sequence, wrapping the combined list of items in parentheses. If desired, a variant on this function can be used to get a different separator between two and/or between the final two as in 'aardvark,1;bananas,2; and cauldrons,3'.

    ( \seq_use:Nn \l__viggo_tmpa_seq {;} )
  }{
    \seq_map_indexed_inline:cn {g_viggo_#2_seq} {
      (##2, ##1)
    } 
  }
  \group_end:
}

By default, \seq_map_indexed_inline:cn doesn't exist, so we create it from the base function \seq_map_indexed_inline:Nn. This allows us to build the sequence name in the commands above since we don't actually know the name.

\cs_generate_variant:Nn \seq_map_indexed_inline:Nn { c }
\ExplSyntaxOff

Test and compare \zzzz, \zzz and \zzzout.

Test \verb|\zzzz|:\par
\zzzz{One}
\zzzz{Two}
\zzzz{Three}
\zzzz{Two}
\zzzz{One}
\medskip
\par
\ExplSyntaxOn \seq_map_indexed_inline:Nn \g_viggo_cites_seq {
  (#2, #1)
} 
\ExplSyntaxOff
Test \verb|\zzz| (default sequence):\par
\zzz{One}
\zzz{Two}
\zzz{Three}
\zzz{Two}
\zzz{One}
\medskip
\par
\ExplSyntaxOn \seq_map_indexed_inline:Nn \g_viggo_citations_seq {
  (#2, #1)
}
\medskip
\par
\ExplSyntaxOff

Test \verb|\zzz[refs]| (custom sequence):\par
\zzz[refs]{One}
\zzz[refs]{Two}
\zzz[refs]{Three,Four,Five}
\zzz[refs]{Two}
\zzz{One}
\medskip
\par
\ExplSyntaxOn \seq_map_indexed_inline:Nn \g_viggo_citations_seq {
  (#2, #1)
} 
\par
\seq_map_indexed_inline:Nn \g_viggo_refs_seq {
  (#2, #1)
} 
\medskip
\par
\ExplSyntaxOff
Test \verb|\zzzout| (one-by-one, default sequence):\par
\zzzout\smallskip\par
Test \verb|\zzzout| (combined, default sequence):\par
\zzzout*\smallskip\par
Test \verb|\zzzout| (one-by-one, custom sequence):\par
\zzzout[refs]\smallskip\par
Test \verb|\zzzout| (combined, custom sequence):\par
\zzzout*[refs]

comparative output of various sorts

Names in expl3

Note that all variable names should use the following format:

\ <scope> _ <module> _ <description> _ <type>

for public variables and

\ <scope> __ <module> _ <description> _ <type>

for private variables.

  • <scope> is usually g (global) or l (local), but it can also be c (constant).
  • <module> should be the name of your package or project or whatever i.e. a (hopefully) unique prefix common to all the variables and functions provided.
  • <description> should describe the purpose of this particular variable.
  • <type> should be the relevant designator for variables of this type.
    • You should not use _flag for booleans, but _bool. Not only is the former not the designated marker for booleans, it is the designated marker for a different variable type altogether.

So e.g. \g_viggo_cites_seq, \l_viggo_tmpa_bool (for a temporary boolean, say) or \g_viggo_cites_prop or whatever.

Note on markup

Finally, you shouldn't use $\lbrack$ etc. to typeset citation labels. These are part of the text and shouldn't be set as maths delimiters. Even if the output looks correct, the markup and underlying character codes are wrong. Depending on the document settings, the output may also be visibly incorrect if, for example, different fonts are used for square brackets in text and mathematics. At the very list, the spacing is likely to be incorrect e.g. kerning and microtype (if you use it) will be wrong.

Complete code:

\documentclass{article}
\pagestyle{empty}
% ateb: https://tex.stackexchange.com/a/721021/
\begin{document}
\ExplSyntaxOn
\seq_new:N \g_viggo_cites_seq
\newcommand{\zzzz}[1]{
  \seq_if_in:NnTF \g_viggo_cites_seq { #1 }
  {
    \seq_map_indexed_inline:Nn \g_viggo_cites_seq
    {
      \str_if_eq:nnT {#1}{##2}
      {
        [ \int_to_arabic:n { ##1 } ]
        \seq_map_break:
      }
    }
  }{
    \seq_gput_right:Nn \g_viggo_cites_seq { #1 }
    [ \int_to_arabic:n { \seq_count:N \g_viggo_cites_seq } ]
  }
}
%%% fancier version
\seq_new:N \l__viggo_tmpa_seq
\NewDocumentCommand \zzz { O {citations} m }
{
  \group_begin:
    \viggo_zzz:nn { #1 } { #2 }
  \group_end:
}
\cs_new_protected:Nn \viggo_zzz:nn
{
  \seq_if_exist:cF { g_viggo_#1_seq }
  {
    \seq_new:c { g_viggo_#1_seq }
  }
  \clist_map_inline:nn { #2 }
  {
    \seq_if_in:cnTF  { g_viggo_#1_seq } { ##1 }
    {
      \seq_map_indexed_inline:cn  { g_viggo_#1_seq }
      {
        \str_if_eq:nnT {##1}{####2}
        {
          [ \int_to_arabic:n { ####1 } ]
          \seq_map_break:
        }
      }
    }{
      \seq_gput_right:cn  { g_viggo_#1_seq } { ##1 }
      [ \int_to_arabic:n { \seq_count:c  { g_viggo_#1_seq } } ]
    }    
  }
}
\NewDocumentCommand \zzzout { s O {citations} }
{
  \group_begin:
  \IfBooleanTF { #1 }
  {
    \seq_clear:N \l__viggo_tmpa_seq
    \seq_map_indexed_inline:cn {g_viggo_#2_seq} {
      \seq_put_right:Nn \l__viggo_tmpa_seq
      {
        ##2, ##1
      }
    }
    ( \seq_use:Nn \l__viggo_tmpa_seq {;} )
  }{
    \seq_map_indexed_inline:cn {g_viggo_#2_seq} {
      (##2, ##1)
    } 
  }
  \group_end:
}
\cs_generate_variant:Nn \seq_map_indexed_inline:Nn { c }
\ExplSyntaxOff

Test \verb|\zzzz|:\par
\zzzz{One}
\zzzz{Two}
\zzzz{Three}
\zzzz{Two}
\zzzz{One}
\medskip
\par
\ExplSyntaxOn \seq_map_indexed_inline:Nn \g_viggo_cites_seq {
  (#2, #1)
} 
\ExplSyntaxOff
Test \verb|\zzz| (default sequence):\par
\zzz{One}
\zzz{Two}
\zzz{Three}
\zzz{Two}
\zzz{One}
\medskip
\par
\ExplSyntaxOn \seq_map_indexed_inline:Nn \g_viggo_citations_seq {
  (#2, #1)
}
\medskip
\par
\ExplSyntaxOff

Test \verb|\zzz[refs]| (custom sequence):\par
\zzz[refs]{One}
\zzz[refs]{Two}
\zzz[refs]{Three,Four,Five}
\zzz[refs]{Two}
\zzz{One}
\medskip
\par
\ExplSyntaxOn \seq_map_indexed_inline:Nn \g_viggo_citations_seq {
  (#2, #1)
} 
\par
\seq_map_indexed_inline:Nn \g_viggo_refs_seq {
  (#2, #1)
} 
\medskip
\par
\ExplSyntaxOff
Test \verb|\zzzout| (one-by-one, default sequence):\par
\zzzout\smallskip\par
Test \verb|\zzzout| (combined, default sequence):\par
\zzzout*\smallskip\par
Test \verb|\zzzout| (one-by-one, custom sequence):\par
\zzzout[refs]\smallskip\par
Test \verb|\zzzout| (combined, custom sequence):\par
\zzzout*[refs]
  
\end{document}
2
  • 1
    Sometimes I write answers I don't end up deleting. Somehow, I doubt this is going to be one of them.
    – cfr
    Commented Jun 21 at 5:12
  • 1
    Thank you for your detailed explanation. :)
    – Viggo
    Commented Jun 21 at 7:05
3

Your issue is with this line:

\exp_args:Nno\prop_put:Nnn \g_my_prop {#2}{\int_use:N \l_citecounter_int}

which expands as follows:

Argument Expansion
\prop_put:Nnn N
\g_my_prop n
{#2} o
{\int_use:N \l_citecounter_int} nothing
(implicit n)

A few issues here:

  • You're using \g_my_prop as an n-type argument; however, n-type arguments must be between {braces}. You probably want N here.

  • You're once expanding { #2 } which isn't incorrect, but probably not what you want. Unless you are doing something very very weird, you should never need to use o-type arguments in expl3.

  • You're not expanding the { \int_use:N … } argument at all, so you're saving the literal text { \int_use:N … } as the property value. Again, not technically incorrect, but almost certainly not what you want here.

  • The expl3 variable naming convention is \<l|g>_<module>_<name>_<type>, with the first underscore doubled for private variables. Most of your variables should probably be private, and they are all missing the module part. So \l_citecounter_int should probably be \l__example_citecounter_int (with example replaced by your package name).

Solutions

  1. Adding another N to your \exp_args:… will make this work:

    \exp_args:NNno \prop_put:Nnn \g_my_prop { #2 } { \int_use:N \l_citecounter_int }
    
  2. You should almost always prefer e-type expansion over o-type expansion, so the following is even better:

    \exp_args:NNne \prop_put:Nnn \g_my_prop { #2 } { \int_use:N \l_citecounter_int }
    
  3. The main reason that expl3 includes the argument types in the macro names is so that you can change the expansion type without needing \exp_args:…, so the following is even better:

    \prop_put:Nne \g_my_prop { #2 } { \int_use:N \l_citecounter_int }
    
  4. The V argument type gets the Value stored in a csname, so you can skip the \int_use:N entirely, giving us the best solution:

    \prop_put:NnV \g_my_prop { #2 } \l_citecounter_int
    

Full example:

\documentclass{standalone}
\usepackage{tikz}

\makeatletter
\newcommand{\ifstringequal}[4]{%
    \ifnum\pdf@strcmp{#1}{#2}=\z@
        #3%
    \else
        #4%
    \fi
}
\makeatother
\begin{document}
\ExplSyntaxOn
    \prop_new:N \g_my_prop
    \bool_new:N \l_my_flag
    \int_new:N \l_citecounter_int
    \int_set:Nn \l_citecounter_int {0}
    \newcommand{\zzzz}[2]{%
        \bool_set_false:N \l_my_flag
        \prop_map_inline:Nn \g_my_prop {
            \ifstringequal{#2}{##1}{
                \bool_set_true:N \l_my_flag%
            }{%
                %donothing
            }
        }
        \bool_if:nTF {\l_my_flag}{% Value already exists in dictionary
            $\lbrack$\exp_args:Nno\prop_item:Nn \g_my_prop{#2}$\rbrack$
        }{%Value didnt exists in dictionary
            \int_incr:N \l_citecounter_int % Increment the counter
            $\lbrack$\int_use:N \l_citecounter_int$\rbrack$
            \prop_put:NnV \g_my_prop { #2 } \l_citecounter_int
        }
    }
\ExplSyntaxOff

Test:\\
\zzzz{opt}{One}
\zzzz{opt}{Two}
\zzzz{opt}{Three}
\zzzz{opt}{Two}
\zzzz{opt}{One}
\par\vspace{2ex}
\ExplSyntaxOn \prop_map_inline:Nn \g_my_prop {
    (#1, #2)
}
\ExplSyntaxOff

\end{document}

output

7
  • 1
    @cfr Most of the expandable expl3 macros require an undocumented/unspecified number of expansion steps to fully-expand them. There are a few macros where the documentation explicitly says “this macro requires N expansion step(s)”, but even then, N is seldom 1. So o will only reliably work for macros that expand in exactly 1 step, while e will work almost 100% of the time. Commented Jun 21 at 0:45
  • 1
    I know it is from the OP, but the syntax in your answer is incorrect: the variables don't follow the naming schema.
    – cfr
    Commented Jun 21 at 0:57
  • 1
    @cfr With classical TeX programming, it's generally best to do as little expansion as possible since there are lots of things that don't behave well in an expansion-only context. But with expl3, any unexpandable macros are defined as \protected, and most of the user input is internally wrapped with \unexpanded, so it's generally pretty safe to fully-expand arbitrary content. Commented Jun 21 at 1:08
  • 1
    @cfr I'm mainly familiar with the LuaTeX internals, but \expanded is pretty cheap: it starts a new token list, expands until a right brace, then links the token list into the current input. \expandafter is fairly cheap too, but it internally calls back_input which forces TeX to process the tokens multiple times. And since you usually need a rather large number of \expandafters, this can sometimes get a little slow. But @Skillmon is the performance expert, so he'd have a much better idea of the performance implications here. Commented Jun 21 at 1:15
  • 1
    Thank you Max, this helps understand my errors and learn something new!
    – Viggo
    Commented Jun 21 at 7:05

You must log in to answer this question.

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