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.

  • 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. – Paul Stanley Mar 29 '17 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 '17 at 7:42
  • @PaulStanley yes, that's a solution but i don't know about the syntax to extract the "head" of the list ... – Hongxu Chen Mar 29 '17 at 7:42
  • @ChristianHupfer i know quite little about LaTeX and expl3. Can you elaborate that? – Hongxu Chen Mar 29 '17 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) – Paul Stanley Mar 29 '17 at 7:47
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}
  • Oh that works! sorry the syntax is really greek to me (i know how to say in other program languages but not in LaTeX)... – Hongxu Chen Mar 29 '17 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... – Hongxu Chen Mar 29 '17 at 7:52
  • @HongxuChen: Done. – user31729 Mar 29 '17 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. – Hongxu Chen Mar 29 '17 at 8:02
  • @HongxuChen: Yes, \myotherlist provides the same functionality but I could not name it \mylist again, of course – user31729 Mar 29 '17 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
    actually I prefer this solution:-) – Hongxu Chen Mar 29 '17 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}
  • really nice package! – Hongxu Chen Mar 31 '17 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.

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.