5

I would like to define a macro for defining further macros to display sequences/lists/tuples of elements by specifying the following parameters:

  1. The name of the new command.
  2. What to display for an empty sequence/list/tuple.
  3. How to open a singleton sequence/list/tuple.
  4. How to close a singleton sequence/list/tuple.
  5. How to open a sequence/list/tuple with more than one element.
  6. How to close a sequence/list/tuple with more than one element.
  7. What to display as separating object between two elements in the sequence/list/tuple.

I had a look at the following questions and corresponding answers:

So I tried to code my command and came up with the following two versions (the second one is outcommented in the code below). Unfortunately, none of them works and I cannot figure out what the problem is:

\documentclass{article}

\makeatletter 
\newcommand{\definelistcommand}[7]{%
\expandafter\newcommand\csname @start#1\endcsname{\expandafter\@ifnextchar\csname @stop#1\endcsname{#2\csname @end#1\endcsname}{\csname @first#1\endcsname}}%
\expandafter\newcommand\csname @first#1\endcsname[1]{\expandafter\@ifnextchar\csname @stop#1\endcsname{#3##1#4\csname @end#1\endcsname}{#5##1\csname @next#1\endcsname}}%
\expandafter\newcommand\csname @next#1\endcsname[1]{#7##1\expandafter\@ifnextchar\csname @stop#1\endcsname{#6\csname @end#1\endcsname}{\csname @next#1\endcsname}}%
\expandafter\newcommand\csname @end#1\endcsname[1]{}% consumes the \stop command
\expandafter\newcommand\csname #1\endcsname[1]{\csname @start#1\endcsname##1\csname @stop#1\endcsname}%
}
\makeatother

% \makeatletter 
% \edef\definelistcommand#1#2#3#4#5#6#7{%
% \noexpand\newcommand\expandafter\noexpand\csname @start#1\endcsname{\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#2\expandafter\noexpand\csname @end#1\endcsname}{\expandafter\noexpand\csname @first#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @first#1\endcsname[1]{\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#3##1#4\expandafter\noexpand\csname @end#1\endcsname}{#5##1\expandafter\noexpand\csname @next#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @next#1\endcsname[1]{#7##1\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#6\expandafter\noexpand\csname @end#1\endcsname}{\expandafter\noexpand\csname @next#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @end#1\endcsname[1]{}% consumes the \stop command
% \noexpand\newcommand\expandafter\noexpand\csname #1\endcsname[1]{\expandafter\noexpand\csname @start#1\endcsname##1\expandafter\noexpand\csname @stop#1\endcsname}%
% }
% \makeatother

\definelistcommand{mylist}{empty}{[}{]}{(}{)}{,}

\begin{document}

\mylist{{a}{b}{c}}

\mylist{{a}{b}}

\mylist{a}

\mylist{{b}}

\mylist{}

\end{document}

The desired output would be:

(a,b,c)
(a,b)
[a]
[b]
empty

For the first version, I get this error message:

! Missing \endcsname inserted.
<to be read again>
                   \let
l.27 \mylist{{a}{b}{c}}

For the second version, I get this:

! Undefined control sequence.
l.27 \mylist
            {{a}{b}{c}}

What are the errors in my commands and how would a correct solution work?

EDIT (to point future readers to the fact that the accepted answer is not the only interesting one): Although I accepted the answer by Christian Hupfer (since it contains an explanation and fix for my attempt), the other answers also contain valuable contributions. In particular, I will use egreg's solution as it allows a key-value pair specification of my desired parameters. Unfortunately, I cannot accept two answers.

  • Wouldn't this be much easier with etoolbox \listadd etc. or with expl3 and its clist or seq features? – user31729 Dec 3 '15 at 19:43
  • @ChristianHupfer I'm not very familiar with etoolbox. If you have a simpler solution with these, just put that as an answer. – cryingshadow Dec 3 '15 at 19:44
  • I'll try, but I am not sure about your purposes. You want a mapper, basically? – user31729 Dec 3 '15 at 19:52
  • I have several list-like structures and would like to have one command for each of these structures by which I can easily define how these structures are displayed (e.g., a sequence of substitutions should be displayed differently than a sequence of states, but each sequence of substitutions should be displayed in the same format). Since the list-like structure is what these structures have in common, I would like to define one generic command for all of them rather than defining several commands in more or less the same way. – cryingshadow Dec 3 '15 at 20:01
  • 1
    I've added a working version with a global \@stoplist command at the end of my solution – user31729 Dec 4 '15 at 12:38
5

Here's a expl3 version with seq variables.

The command \NewListCommand declares the command list macro named like the first argument and uses a global seq variable with some prefix, see \g_cryingshadow_mynewlist_seq etc.

This is filled by the \mynewlist command and the content displayed depending on the content of the other parameters from #3 to #6. The 7th. parameter is used as a delimiter

Some notes: The empty parameter could be an optional one.

\documentclass{article}


\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\NewListCommand}{mmmmmmm}{%
  \seq_new:c {g_cryingshadow_#1_seq} % Make a new sequence
  \expandafter\NewDocumentCommand\csname #1\endcsname{m}{%
    \seq_gclear:c {g_cryingshadow_#1_seq}% Clear the sequence
    \seq_gclear:N \l_tmpa_seq % Clear a temporary `\l_tmpa_seq
    \seq_set_split:Nnn \l_tmpa_seq {} {##1} % Split the sequence into tokens. 
    \seq_set_eq:cN {g_cryingshadow_#1_seq} \l_tmpa_seq % copy the temporary sequence to the real one → this would not be necessary if the sequence is not be used outside 
    \seq_if_empty:cTF {g_cryingshadow_#1_seq} {%
      #2% Checked --> is empty
    }{%
      % Display the various styles
      \int_case:nn { \seq_count:c {g_cryingshadow_#1_seq} }
      {%
        {1} {#3      \seq_use:cnnn {g_cryingshadow_#1_seq} {,} {#7} {#7} #4}
      }%
      \int_compare:nNnT { \seq_count:c {g_cryingshadow_#1_seq}}  > 1 {%
        #5      \seq_use:cnnn {g_cryingshadow_#1_seq} {#7} {#7} {#7} #6
      }
    }
  }
}
\ExplSyntaxOff


\NewListCommand{mynewlist}{empty}{[}{]}{(}{)}{,}


\begin{document}

\mynewlist{{A}{B}{C}}

\mynewlist{{A}{B}}

\mynewlist{{a}}

\mynewlist{}


\mynewlist{{A}{B}{C}{D}{E}{F}}


Astronomer's alphabet:

\mynewlist{{Oh}{be}{a}{fine}{girl}{kiss}{me}}

\end{document}

enter image description here

Edit: Here's the working version for usual LaTeX:

\documentclass{article}
\usepackage{etoolbox}

\makeatletter

\def\definelistcommand#1#2#3#4#5#6#7{%

  \expandafter\def\csname @firstinlist#1\endcsname##1{%
    \@ifnextchar\@stoplist{%
      #3##1#4\@endlist%
    }{%
      #5##1\csname @nextlist#1\endcsname%
    }%
  }

  \expandafter\def\csname @nextlist#1\endcsname##1{%
    \@ifnextchar\@stoplist{%
      #7##1#6\@endlist%
    }{%
      #7##1\csname @nextlist#1\endcsname%
    }%
  }


  \expandafter\def\csname @startlist#1\endcsname##1{%
    \@ifnextchar\@stoplist{%
      #3##1#4\@endlist%
    }{%
      \csname @firstinlist#1\endcsname##1% 
    }%
  }

  \expandafter\def\csname #1\endcsname##1{%
    \ifblank{##1}{%
      #2%
    }{%
      \csname @startlist#1\endcsname##1\@stoplist%
    }%
  }
}

\def\@endlist#1{}


\definelistcommand{mycmd}{empty}{[}{]}{(}{)}{,}

\definelistcommand{othercmd}{empty}{\textbraceleft}{\textbraceright}{(}{)}{+}

\begin{document}


\makeatother

\mycmd{{A}{B}{C}}

\mycmd{A}

\mycmd{{A}{B}}

\mycmd{}

\othercmd{{O}{B}}

\othercmd{{O}}


\end{document}
  • Thanks! Could you explain your solution a little bit? I'm not very familiar with xparse etc. and would like to get an idea how and why this works as it does (and maybe then also get an idea why my solution does not). – cryingshadow Dec 3 '15 at 20:37
  • 1
    @cryingshadow: I wished I could explain why your versions do not work, at the moment I can't. I'll add some explanations about the code into the code – user31729 Dec 3 '15 at 20:40
  • 1
    @cryingshadow: I'll use xparse and expl3 very frequently, although I am no expert of course -- but I can recommend it, xparse itself is easy to learn – user31729 Dec 3 '15 at 20:48
  • @cryingshadow: You constantly call \csname @stop#1\endcsname This is never defined (so the result is \relax.) – user31729 Dec 3 '15 at 21:17
  • True, but it should never be executed - it should be consumed by the @end#1 command. – cryingshadow Dec 3 '15 at 21:19
8

You can try this macro:

\newcount\tmpnum
\def\definelistcommand#1#2#3#4#5#6#7{%
   \expandafter\def\csname\string#1:list\endcsname##1{%
      \ifcase##1 #2\or#3\or#4\or#5\or#6\or#7\fi}%
   \def#1{\dolist#1}%
}
\def\dolist#1#2{\tmpnum=0 \def\tmp{}%
   \def\listcommand{\csname\string#1:list\endcsname}%
   \dolistA#2\end}
\def\dolistA#1{%
   \ifx\end#1%
      \ifnum\tmpnum=0 \listcommand0%
      \else\ifnum\tmpnum=1 \listcommand1\tmp\listcommand2%
      \else \listcommand3\tmp\listcommand4%
      \fi\fi
   \else\edef\tmp{\ifx\tmp\empty\else\tmp\listcommand5\fi#1}%
      \advance\tmpnum by1
      \expandafter\dolistA\fi
}

\definelistcommand \mylist {empty} [](),

\mylist{{a}{b}{c}}

\mylist{{a}{b}}

\mylist{a}

\mylist{{b}}

\mylist{}

Explanation. The \definelistcommand\foo defines two control sequences. First one \\foo:list as \ifcase. It expands the appropriate declared parameter: \\foo:list0 expnads to nothing, \\foo:list1 to [ etc. Second defined control sequence is \foo. This is macro \dolist\foo.

The usage of \foo{text} expands to \dolist\foo{text}. This macro resets \tmp to empty, \tmpnum to zero and saves \listcommand as \\foo:list. Then it runs \dolistA text\end.

The \dolistA reads the items of the text repeatedly. It saves these items to the \tmp macro using \edef\tmp. If \tmp is not empty then the comma (alias \listcommand5) is added between the previous contents and the next item #1. The \tmpnum counts the number of items. When \end is reached then \tmp is printed depending on the \tmpnum value.

Note that we need not any external package and we can declare the list-like macro very simply.

  • Thanks, that works. Could you explain a little bit how it works? It seems you are using a counter for the number of elements and that several further commands are defined which might have conflicting names with other user-defined commands. – cryingshadow Dec 3 '15 at 20:12
  • 1
    @cryingshadow If there will be more votes then I'll add the explanation. I see more votes in LaTeX 3 solution (which is absurd language), but solutions based only on classical TeX is not evaluated here. And to conflicting names with other user-defined commands: what is \hbox, \vskip etc? They are conflicting names with user-defined commands too. – wipet Dec 3 '15 at 22:19
  • I can only vote once... ;) I am just interested in how it works and what to keep in mind when using it (that is what the part on conflicting names is about - it was not intended as a criticism against your solution). – cryingshadow Dec 3 '15 at 22:25
6

Rather than a long list of arguments, I think it's better to use a key-value syntax.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\definelistcommand}{mm}
 {% #1 is the command name, #2 is the key-value setup
  % clear the temporary property list
  \prop_clear:N \l_cryingshadow_list_temp_prop
  \keys_set:nn { cryingshadow/list } { #2 }
  % check if left-single or right-single have been set
  \prop_if_in:NnF \l_cryingshadow_list_temp_prop { left-single }
   {
    \prop_put:Nnx \l_cryingshadow_list_temp_prop { left-single }
     { \prop_item:Nn \l_cryingshadow_list_temp_prop { left } }
   }
  \prop_if_in:NnF \l_cryingshadow_list_temp_prop { right-single }
   {
    \prop_put:Nnx \l_cryingshadow_list_temp_prop { right-single }
     { \prop_item:Nn \l_cryingshadow_list_temp_prop { right } }
   }
  % allocate a specific property list and make it equal to the temporary one
  \prop_new:c { g_cryingshadow_list_#1_prop }
  \prop_gset_eq:cN { g_cryingshadow_list_#1_prop } \l_cryingshadow_list_temp_prop
  % define the runtime macro to call the generic one with the current name as argument
  \cs_new_protected:cpn { #1 } ##1 { \cryingshadow_list_print:nn { #1 } { ##1 } }
 }

% allocate some variables
\prop_new:N \l_cryingshadow_list_temp_prop
\seq_new:N \l_cryingshadow_list_input_seq

% syntactic sugar
\cs_new_protected:Nn \__cryingshadow_list_put:nn
 {
  \prop_put:Nnn \l_cryingshadow_list_temp_prop { #1 } { #2 }
 }
\cs_new_protected:Nn \__cryingshadow_list_item:nn
 {
  \prop_item:cn { g_cryingshadow_list_#1_prop } { #2 }
 }

% define the keys
\keys_define:nn { cryingshadow/list }
 {
  empty .code:n = \__cryingshadow_list_put:nn { empty } { #1 },
  left-single .code:n = \__cryingshadow_list_put:nn { left-single } { #1 },
  right-single .code:n = \__cryingshadow_list_put:nn { right-single } { #1 },
  left .code:n = \__cryingshadow_list_put:nn { left } { #1 },
  right .code:n = \__cryingshadow_list_put:nn { right } { #1 },
  delimiter .code:n = \__cryingshadow_list_put:nn { delimiter } { #1 },
 }

\cs_new_protected:Nn \cryingshadow_list_print:nn
 {
  % split the input into items at commas
  \seq_set_split:Nnn \l_cryingshadow_list_input_seq { , } { #2 }
  % branch between empty or single and multiple items
  \int_compare:nTF { \seq_count:N \l_cryingshadow_list_input_seq = 1 }
   {
    % if the sequence has just one item it could be empty
    \tl_if_blank:nTF { #2 }
     { \__cryingshadow_list_item:nn { #1 } { empty } }
     {
      \__cryingshadow_list_item:nn { #1 } { left-single }
      #2
      \__cryingshadow_list_item:nn { #1 } { right-single }
     }
   }
   {
    % otherwise it has multiple items
    \__cryingshadow_list_item:nn { #1 } { left }
    \seq_use:Nn \l_cryingshadow_list_input_seq { \__cryingshadow_list_item:nn { #1 } { delimiter } }
    \__cryingshadow_list_item:nn { #1 } { right }
   }
 }

\ExplSyntaxOff

\definelistcommand{firstlist}{
  empty=empty,
  left-single=[,
  right-single=],
  left=(,
  right=),
  delimiter={, },
}
\definelistcommand{secondlist}{
  empty=nothing,
  left=\{,
  right=\},
  delimiter={--},
}

\begin{document}

\firstlist{A,B,C}

\firstlist{A,B}

\firstlist{a}

\firstlist{}

\secondlist{A,B,C}

\secondlist{A,B}

\secondlist{a}

\secondlist{}

\end{document}

enter image description here

How does this work?

The \definelistcommand first sets up a property list specific for the list command to be defined, where to store the various items (outer delimiters, inner delimiters, what to do for the empty case); then it defines the macro to call a generic function for producing the output.

The property list is supplied the values by examining the second argument. If left-single or right-single are not specified, the values for left or right are supplied.

At call time, the generic macro takes the values from the specific property list, splits the argument at commas and then branches: if the argument is blank, the value for the case is printed; if there's only one item, the left-single and right-single outer delimiters are used; otherwise the left and right ones, with the inner delimiter between items.

  • I really like the idea of key-value pairs. Seems I have to dive into xparse and friends for sophisticated macros... – cryingshadow Dec 3 '15 at 21:31
  • 1
    @cryingshadow You can also easily supplement the list of keys, for instance you can add a key for the list separator at run time; or maybe define a constant property list with default values, so you can specify only the ones that are to be changed; you can add a check for the “single” keys and, if missing, use the “nonsingle” ones. – egreg Dec 3 '15 at 21:35
  • @cryingshadow I made some changes along the lines of my previous comment. – egreg Dec 3 '15 at 21:51
  • I just noted one thing in this solution that is problematic in my setting: It seems that this command "breaks out" of math mode. So I cannot just use it within math mode as I can, e.g., with the solution by @wipet. – cryingshadow Dec 3 '15 at 22:02
  • @cryingshadow I have no problem and, no, it doesn't do differently in text or math mode; of course, if you plan to use \langle and \rangle as outer delimiters and the macro in math mode, you shouldn't say left=$\langle$, but left=\langle (or left=\ensuremath{\langle}). – egreg Dec 3 '15 at 22:05

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.