16

I'm trying to typeset an OCaml like list and would like to define a command mylist for it. The expected result is something like below:

\mylist{}          -->     []
\mylist{aa}        -->     [aa]
\mylist{aa,bb}     -->     [aa;bb]
\mylist{aa,bb,cc}  -->     [aa;bb;cc]

I expect that , is always the typed separator (in LaTeX) but is displayed as another sign (here it is ;) in the pdf document (it's acceptable to hardcode the displayed one in mylist definition).

After searching for a while I found this answer and changed it a bit.

\newcounter{listcount}\newcounter{totalcount}%
\newcommand{\mylist}[1]{%
  \setcounter{totalcount}{0}% Reset total count
  \renewcommand*{\do}[1]{\stepcounter{totalcount}}% Reconfigure count
  \docsvlist{#1}% Count number of items
  \setcounter{listcount}{0}% Reset current item count
  \renewcommand*{\do}[1]{% Reconfigure item \do
    \stepcounter{listcount}% Next item
    ##1;\ifnum\value{listcount}<\value{totalcount}\,\fi% Print item
  }
  \left[\docsvlist{#1}\right]% Process list
}

The problem is that when the list contains >1 elements, there is a trailing ;.

\mylist{aa,bb} --> [aa;bb;]

Intuitively there is a need to test whether the element is the last one. However I seem lost in the macro definition w.r.t. listcount and the number of commas.

I also found a use of forcsvlist but the problem is similar. There seems a solution by using other packages, but a bit overkill so I tend not to use that.

5
  • 1
    Can't you deal with the first item as a special case and then add your semicolon before each other item. Then you only need to test for item 1. Mar 29, 2017 at 7:41
  • I used \listadd etc. in earlier times and switched to \seq lists from expl3 -- it's much easier and not really overkill ;-)
    – user31729
    Mar 29, 2017 at 7:42
  • @PaulStanley yes, that's a solution but i don't know about the syntax to extract the "head" of the list ... Mar 29, 2017 at 7:42
  • @ChristianHupfer i know quite little about LaTeX and expl3. Can you elaborate that? Mar 29, 2017 at 7:44
  • Can't you forget your first counting loop and simply change your ifnum test to less than 1 (moving the counter increment to after it) Mar 29, 2017 at 7:47

7 Answers 7

10

The character ; must be shifted to be placed inside of the \ifnum\value....\fi test.

I also added a much, much, much shorter expl3 version right before \begin{document}, although the expl3 syntax might be very strange to L3 newbies...

I used the \docsvlist and \forcsvlist macros up to two years (2015) ago, but shifted then to he much more powerful expl3 syntax (although not easy to learn), especially for the multitude of lists there are in expl3, such as seq or clist or prop lists, which provide more functionality, in my point of view.

But first to the \ifnum... test:

 \ifnum\value{listcount} < \value{totalcount}
 ;\,
 \fi

just tests whether the current value of the listcount counter is smaller than the value of totalcount counter and prints a ;\, then, otherwise it doesn't.

The expl3 version is at the end and much simpler in its usage, no tests are needed (in fact, they are hidden behind \seq_use).

First enter the expl3 - syntax style with \ExplSyntaxOn and store the comma separated list values of the argument to a temporary sequence variable \l_tmpa_seq. Later display this list with \seq_use:Nn \l_tmpa_seq {;\,} -- the command takes care about the end of the list.

In order to prevent space gobbling, the wrapper command \displaylist is defined outside, see also Spaces in l3prop for the topic of spaces with expl3.


\documentclass{article}

\usepackage{etoolbox}

\newcounter{listcount}
\newcounter{totalcount}%
\newcommand{\mylist}[1]{%
  \setcounter{totalcount}{0}% Reset total count
  \renewcommand*{\do}[1]{\stepcounter{totalcount}}% Reconfigure count
  \docsvlist{#1}% Count number of items
  \setcounter{listcount}{0}% Reset current item count
  \renewcommand*{\do}[1]{% Reconfigure item \do
    \stepcounter{listcount}% Next item
    ##1\ifnum\value{listcount}<\value{totalcount};\,\fi% Print item
  }
  \left[\docsvlist{#1}\right]% Process list
}


\begin{document}
$\mylist{aa,bb,cc}$

$\mylist{aa}$


\end{document}

Of course $\mylist{}$ will yield [].

enter image description here

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn
\newcommand{\myotherlist}[1]{%
  \seq_set_from_clist:Nn \l_tmpa_seq  {#1}
  \displaylist{\seq_use:Nn \l_tmpa_seq {;\,}}
}
\ExplSyntaxOff
\newcommand{\displaylist}[1]{%
  \left[#1\right]%
}

\begin{document}

$\myotherlist{}$

$\myotherlist{aa,bb,cc}$

$\myotherlist{aa}$

\end{document}
5
  • Oh that works! sorry the syntax is really greek to me (i know how to say in other program languages but not in LaTeX)... Mar 29, 2017 at 7:49
  • can you explain a bit about the \ifnum syntax. I googled a while but haven't found an easy-to-understand tutorial. really a noob question... Mar 29, 2017 at 7:52
  • @HongxuChen: Done.
    – user31729
    Mar 29, 2017 at 7:59
  • so basically \myotherlist just does a replacement? I think I will still stick to the previous solution since LaTeX3 syntax is difficult for me and I don't know how to get started to customize myself. Mar 29, 2017 at 8:02
  • @HongxuChen: Yes, \myotherlist provides the same functionality but I could not name it \mylist again, of course
    – user31729
    Mar 29, 2017 at 8:04
11

Here is an alternative solution which does not need to iterate the list twice. After the first element, the \do is replaced by \mydo:

\documentclass{article}

\usepackage{etoolbox}

\newcommand*{\mydo}[1]{;\,#1}

\newcommand{\mylist}[1]{%
  \renewcommand*{\do}[1]{##1\let\do\mydo}%
  \left[\docsvlist{#1}\right]% Process list
}

\begin{document}

$\mylist{aa,bb,cc}$

$\mylist{aa}$

\end{document}
1
  • 1
    actually I prefer this solution:-) Mar 29, 2017 at 8:17
8

A fully expandable version:

\documentclass{article}

\makeatletter
\newcommand{\ocamllist}[1]{\left[\ocaml@list#1,,\@nil}
\def\ocaml@list#1,#2,#3\@nil{%
  \if\relax\detokenize{#2}\relax
    \expandafter\@firstoftwo % no comma
  \else
    \expandafter\@secondoftwo
  \fi
  {\if\relax\detokenize{#1}\relax\,\else#1\fi\right]}
  {#1;\ocaml@list#2,#3,\@nil}
}
\makeatother

\begin{document}

$\ocamllist{}$

$\ocamllist{aa}$

$\ocamllist{aa,bb}$

$\ocamllist{aa,bb,cc}$

\end{document}

enter image description here

Not expandable, but more compact:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\ocamllist}{m}
 {
  \left[
  \clist_set:Nn \l_hongxu_ocamllist_clist {#1}
  \clist_if_empty:NTF \l_hongxu_ocamllist_clist
   { \, }
   { \clist_use:Nn \l_hongxu_ocamllist_clist {;} }
  \right]
 }
\clist_new:N \l_hongxu_ocamllist_clist
\ExplSyntaxOff

\begin{document}

$\ocamllist{}$

$\ocamllist{aa}$

$\ocamllist{aa,bb}$

$\ocamllist{aa,bb,cc}$

\end{document}
8

May be I have not understood the problem, but instead of getting dizzy counting the items of the list and so... why not just take the argument as one string where you must replace the commas by semicolons?

mwe

\documentclass{article}

\usepackage{xstring}
\def\mylist#1{\ensuremath{\mathbin{[\StrSubstitute{#1}{,}{;}]}}}
\begin{document}

\mylist{}           

\mylist{aa}         

\mylist{aa,bb}      

\mylist{aa,bb,cc}   

\end{document}
1
  • really nice package! Mar 31, 2017 at 8:47
4

Here is an expanable solution using only TeX primitives:

\def\ocamlist#1{\ocamlistA #1,,}
\def\ocamlistA#1,{[#1\ocamlistB}
\def\ocamlistB#1,{\ifx,#1,#1]\else;#1\expandafter\ocamlistB\fi}

$\ocamlist{aa,bb}$

\end
1

Here is a listofitems approach.

\documentclass{article}
\usepackage{listofitems}
\newcommand\mylist[1]{%
  [\ifx\relax#1\relax\else
  \readlist*\MyList{#1}%
  \foreachitem\i\in\MyList{\ifnum\icnt>1;\fi\i}\fi]%
}
\begin{document}
\mylist{}

\mylist{aa}

\mylist{aa,bb}

\mylist{aa,bb,cc}
\end{document}

enter image description here

If the space following the punctuation is important,

\documentclass{article}
\usepackage{listofitems}
\newcommand\mylist[1]{%
  [\ifx\relax#1\relax\else
  \readlist*\MyList{#1}%
  \foreachitem\i\in\MyList{\ifnum\icnt>1; \fi\i}\fi]%
}
\begin{document}
\mylist{}

\mylist{aa}

\mylist{aa,bb}

\mylist{aa,bb,cc}
\end{document}

enter image description here

0

This falls under Cunning (La)TeX tricks:

[aa;bb;cc]
[aa]
[]
[aa:bb:cc:dd]

\documentclass{article}

\usepackage{etoolbox}

\newcommand{\mylist}[2][;]{%
  \def\itemdelim{\def\itemdelim{#1}}% https://tex.stackexchange.com/a/89187/5764
  \renewcommand*{\do}[1]{\itemdelim##1}%
  [\docsvlist{#2}]% Process list
}

\begin{document}

\mylist{aa,bb,cc}

\mylist{aa}

\mylist{}

\end{document}

The principle is similar to Thomas' answer and delays the setting of \itemdelim by a single iteration, \defining it as a (re)definition. This way, at it's first use, it redefines \itemdelim without setting anything. Subsequent uses (if they exist) will actually set \itemdelim.

You must log in to answer this question.

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