27

I have a comma-separated list, stored in a macro. It is non-empty, and it has commas between the elements but not at the start or end. How can I get:

  1. the first element (a.k.a. the "head")?
  2. the last element?
  3. every element except the first (a.k.a. the "tail")?

To clarify, I would like commands \head, \last and \tail, such that if

\johnlist={2,3,5,7,11}

then

\head\johnlist=2
\last\johnlist=11
\tail\johnlist={3,5,7,11} 

By the way, I would prefer a TikZ-based solution if that's possible. Or, at least, a solution that works in the context of a TikZ picture.

10 Answers 10

12

xstring solution

\documentclass{article}
\usepackage{xstring}

\def\johnlist{2,3,5,7,11,13}
\def\splicelist#1{
\StrCount{#1}{,}[\numofelem]
\ifnum\numofelem>0\relax
    \StrBefore[1]{#1}{,}[\myhead]%
    \StrBehind[1]{#1}{,}[\mytail]%
    \StrBehind[\numofelem]{#1}{,}[\mylast]%
\else
    \let\myhead#1%
    \let\mylast#1%
    \def\mytail{N/A}
\fi
}

\begin{document}
\splicelist{\johnlist}
\myhead\par
\mylast\par
\mytail


\splicelist{1}
\myhead\par
\mylast\par
\mytail

\end{document}

enter image description here

  • Thanks percusse, this is really appealingly simple! I've updated my question to ask now for the head, the tail, and the last element. Would you care to extend your answer to explain how to get the last element of my list? – John Wickerson May 24 '13 at 6:46
  • @JohnWickerson I can in the evening but in the meantime you can look up for counting the commas in the list and save it to a macro (you can put [\numcommas] at the end of the xstring macros to save the result to a macro) and change the the optional argument in \StrBehind to that number which will give you the remaining element after the last comma. – percusse May 24 '13 at 7:20
  • Yes, I see. By the way, your code doesn't work when my list has just a single element, e.g. \def\johnlist{2}, because then \StrBefore gives the empty string. I looked at texdoc xstring and it seems the \StrCut might be a better option here. – John Wickerson May 24 '13 at 8:04
  • @JohnWickerson The edit is a little more robust but doesn't expand any braced groups etc. so it is still not foolproof. – percusse May 24 '13 at 22:03
  • Actually there is a variable naming mistake; the number of elements is one larger than the number of commas so that's arithmetical stupiditiy on my side though it doesn't effect the functionality. – percusse May 24 '13 at 22:12
19

Assuming that the list is contained in a macro, otherwise you need nothing to extract a first item that you know explicitly, you can do

\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\extractfirst}{mm}
 {
  \tl_set:Nx #1 {\clist_item:Nn #2 { 1 } }
 }
\ExplSyntaxOff

Complete example:

\documentclass{article}
\usepackage{tikz}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\extractfirst}{mm}
 {
  \tl_set:Nx #1 {\clist_item:Nn #2 { 1 } }
 }
\ExplSyntaxOff

\begin{document}
  \def\johnlist{2,3,5,7}
  \begin{tikzpicture}
    \extractfirst\x\johnlist
    \node[shape=circle,draw=black] at (\x,0) {\x};
  \end{tikzpicture}
\end{document}

enter image description here

It's easy also to extract the last element; just add

\NewDocumentCommand{\extractlast}{mm}
 {
  \tl_set:Nx #1 {\clist_item:Nn #2 { -1 } }
 }

In order to get the tail of the list, you can pop the first element from the clist

\NewDocumentCommand{\extracttail}{mm}
 {
  \clist_set:NV #1 #2
  \clist_pop:NN #1 \l_tmpa_tl
 }

Here's the example:

\documentclass{article}
\usepackage{tikz}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\extractfirst}{mm}
 {
  \tl_set:Nx #1 {\clist_item:Nn #2 { 1 } }
 }
\NewDocumentCommand{\extracttail}{mm}
 {
  \clist_set_eq:NN #1 #2
  \clist_pop:NN #1 \l_tmpa_tl
 }
\NewDocumentCommand{\extractlast}{mm}
 {
  \tl_set:Nx #1 {\clist_item:Nn #2 { -1 } }
 }
\ExplSyntaxOff

\begin{document}
  \def\johnlist{2,3,5,7}
  \begin{tikzpicture}
    \extractfirst\x\johnlist
    \node[shape=circle,draw=black] at (\x,0) {\x};
  \end{tikzpicture}

  \begin{tikzpicture}
    \extracttail\x\johnlist
    \foreach \i in \x {
      \node[shape=circle,draw=black] at (\i,0) {\i};
    }
  \end{tikzpicture}

\end{document}

enter image description here

  • We don't have clist_head or clist_tail functions in expl3 because we take the approach that clists are only for user input and not for further processing, so there is no built-in way to get the tail. Assuming that the clist is already normalized to some extent, one could write \int_compare:nNnF { \clist_count:n {#1} } < 2 { \jb_skip:w #1 }, where \jb_skip:w is defined as \cs_new:Npn \jb_skip:w #1 , {}. – Bruno Le Floch May 23 '13 at 16:02
  • @BrunoLeFloch You forget \clist_item:Nn \x_y_clist { -1 } – egreg May 23 '13 at 16:08
  • I think that John Wickerson wants all items except the first. And we haven't provided a function to extract a range of items of a clist (we have that for strings). – Bruno Le Floch May 23 '13 at 16:09
  • @BrunoLeFloch I see, now. – egreg May 23 '13 at 16:10
  • @egreg Ah, I had forgotten \clist_pop. If I remember correctly, \clist_set_eq should exist, so the solution you gave with \clist_pop can be made into clean expl3 (and no need for the map_inline version). – Bruno Le Floch May 23 '13 at 16:37
17

PGFMath has an array parser which can be used for this. The syntax is {comma delimited list}[index]. You need the outer braces so to use it on \johnlist you'd need to add the braces in. As coordinates are passed through pgfmath then you can use this as-is in coordinate specifications, in other uses you need to use \pgfmathparse (or one of its variants).

\documentclass{article}
\usepackage{tikz}
\begin{document}
  \def\johnlist{2,3,5,7}
  \begin{tikzpicture}
\node[shape=circle,draw=black] at ({\johnlist}[0],0) {\pgfmathparse{{\johnlist}[0]}\pgfmathresult};
  \end{tikzpicture}   
\end{document}

Getting the tail involves counting how many entries there are in the list. Fortunately, PGFMath has a function for this as well. (This does not seem to be in the manual, I came across it when looking in the code to see how it knew where the end of the array was.) The function is dim(<array>). This gives the total number of entries so as PGF arrays are 0-indexed you need to subtract 1 to get the last element.

Full code:

\documentclass{article}
%\url{http://tex.stackexchange.com/q/115730/86}
\usepackage{tikz}
\begin{document}
  \def\johnlist{2,3,5,7}
  \begin{tikzpicture}

\node[shape=circle,draw=black] at ({\johnlist}[0],0)
{\pgfmathparse{{\johnlist}[0]}\pgfmathresult};

\node[shape=circle,draw=black] at
({{\johnlist}[dim({\johnlist})-1]},0)
{\pgfmathparse{{\johnlist}[dim({\johnlist})-1]}\pgfmathresult};

  \end{tikzpicture}   
\end{document}

Note the extra braces when in the coordinate to protect the inner () from confusing the parser.

(Added later) Looks like I misunderstood the meaning of the word "tail". This is stretching PGF's array handling quite a bit so I would seriously recommend jumping to LaTeX3 very soon. Nevertheless, it is possible to get a dynamic tail:

\pgfmathsetmacro\len{dim({\johnlist})-1}
\foreach[evaluate=\x as \x using {{\johnlist}[\x]}] \x in {1,...,\len}
\node[draw=red] at (\x,0) {\x};

This could be used more generally to get an array slice of some fashion.

(Added again)

The request in comments was for a proper slice so that one could save the slice of an array for reuse. This does not exist in PGFMath, but with a little hackery it can be implemented. It does not have a shortcut notation, and it doesn't behave well when mixed with the array element shortcut notation. Moreover, PGFMath internally converts lists into token lists so if we're staying within a PGFMath context we need to return a token list but if we want to save the list for reuse we need to return a comma-separated list. So there are two functions splice and csvsplice for these two contexts.

\documentclass{article}
%\url{http://tex.stackexchange.com/q/115730/86}
\usepackage{tikz}

\makeatletter
\pgfmathdeclarefunction{slice}{3}{%
  \begingroup%
  \def\pgfmath@csv{}%
  \afterassignment\pgfmath@gobbletilpgfmath@\pgfmath@count=#2\relax\pgfmath@%
  \afterassignment\pgfmath@gobbletilpgfmath@\c@pgfmath@counta=#3\relax\pgfmath@%
  \advance\c@pgfmath@counta by -\pgfmath@count\relax
  \expandafter\pgfmathslice@@#1\pgfmath@stop%
}

\pgfmathdeclarefunction{csvslice}{3}{%
  \begingroup%
  \def\pgfmath@csv{,}%
  \afterassignment\pgfmath@gobbletilpgfmath@\pgfmath@count=#2\relax\pgfmath@%
  \afterassignment\pgfmath@gobbletilpgfmath@\c@pgfmath@counta=#3\relax\pgfmath@%
  \advance\c@pgfmath@counta by -\pgfmath@count\relax
  \expandafter\pgfmathslice@@#1\pgfmath@stop%
}

\def\pgfmathslice@@#1{%
  \def\pgfmath@temp{#1}%
  \advance\pgfmath@count by-1\relax%
  \ifx\pgfmath@temp\pgfmath@token@stop%
  \pgfmath@error{Initial array index out of bounds.}{}%
  \def\pgfmathresult{0}%
  \pgfmath@smuggleone\pgfmathresult\endgroup%
  \let\pgfmath@next=\relax%
  \else%
  \ifnum\pgfmath@count=-1\relax%
  \pgfmath@count=\c@pgfmath@counta
  \def\pgfmathresult{{#1}}%
  \let\pgfmath@next=\pgfmathslice@@@%
  \else%
  \let\pgfmath@next=\pgfmathslice@@%
  \fi%
  \fi%
  \pgfmath@next}

\def\pgfmathslice@@@#1{%
  \def\pgfmath@temp{#1}%
  \advance\pgfmath@count by-1\relax%
  \ifnum\pgfmath@count=-1\relax%
   \ifx\pgfmath@temp\pgfmath@token@stop%
    \pgfmathslice@@@@\pgfmath@stop
    \let\pgfmath@next=\relax%
   \else%
    \let\pgfmath@next=\pgfmathslice@@@@%
   \fi
  \else%
   \ifx\pgfmath@temp\pgfmath@token@stop%
    \pgfmath@error{Final array index out of bounds.}{}%
    \pgfmath@smuggleone\pgfmathresult\endgroup%
    \let\pgfmath@next=\relax%
   \else%
    \expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\pgfmathresult\expandafter\expandafter\expandafter{\expandafter\pgfmathresult\pgfmath@csv{#1}}%
    \let\pgfmath@next=\pgfmathslice@@@%
   \fi%
  \fi%
  \pgfmath@next}

\def\pgfmathslice@@@@#1\pgfmath@stop{%
  \expandafter\def\expandafter\pgfmathresult\expandafter{\expandafter{\pgfmathresult}}%
  \pgfmath@smuggleone\pgfmathresult\endgroup%
}

\makeatother
  \def\johnlist{2,3,5,7}

\pgfmathparse{slice({\johnlist},0,2)}
\show\pgfmathresult
\pgfmathparse{slice({\johnlist},1,2)}
\show\pgfmathresult
\pgfmathparse{slice({\johnlist},1,3)}
\show\pgfmathresult
\pgfmathparse{csvslice({\johnlist},1,2)}
\show\pgfmathresult
\let\newlist=\pgfmathresult
\pgfmathparse{\newlist[1]}
\show\pgfmathresult
\pgfmathparse{csvslice({\johnlist},1,3)}
\show\pgfmathresult
\pgfmathparse{array(slice({\johnlist},1,2),1)}
\show\pgfmathresult
\pgfmathparse{array({{3},{5}},1)}
\show\pgfmathresult

\pgfmathparse{csvslice({\johnlist},1,dim({\johnlist})-1)}
\show\pgfmathresult

\begin{document}
  \begin{tikzpicture}

\node[shape=circle,draw=black] at ({\johnlist}[0],0)
{\pgfmathparse{{\johnlist}[0]}\pgfmathresult};

\node[shape=circle,draw=black] at
({{\johnlist}[dim({\johnlist})-1]},0)
{\pgfmathparse{{\johnlist}[dim({\johnlist})-1]}\pgfmathresult};

\foreach[evaluate=\x as \x using {{\johnlist}[\x]}] \x in {1,...,\len}
\node[draw=red] at (\x,0) {\x};

  \end{tikzpicture}   
\end{document}

(Did I mention that I think you should use LaTeX3 for this?)

  • Great, this suits me very well. I've taken the liberty of changing {\johnlist}[1] to {\johnlist}[0] in your answer. (Turns out that arrays are 0-based.) – John Wickerson May 23 '13 at 12:14
  • @JohnWickerson I've been doing too much lua recently ... – Loop Space May 23 '13 at 12:51
  • I have updated my question to generalise it to getting the tail as well. If you have the time, would you care to address that in your answer too? Thanks! – John Wickerson May 23 '13 at 15:52
  • @JohnWickerson Answer updated to include tails. – Loop Space May 23 '13 at 16:06
  • @JohnWickerson Misunderstood the type of tail ... – Loop Space May 23 '13 at 16:22
9

Here is an etoolbox approach which provides \listhead and \listtail:

enter image description here

\documentclass{article}
\usepackage{etoolbox}% http://ctan.org/pkg/etoolbox
\newcommand{\listhead}[1]{%
  \renewcommand*{\do}[1]{##1\renewcommand*{\do}[1]{}}% Manage each item and print head
  \expandafter\docsvlist\expandafter{#1}% Process list
}
\newcommand{\listtail}[1]{%
  \renewcommand*{\do}[1]{\gdef\listtailitem{##1}}% Manage each item
  \expandafter\docsvlist\expandafter{#1}% Process list
  \listtailitem% Print tail
}
\begin{document}
\def\johnlist{2,3,5,7}
Head: \listhead{\johnlist} \par
Tail: \listtail{\johnlist}
\end{document}

Both methods parse the entire list (one drawback of \tailhead).

  • 1
    How about getting all items but the first? (I think that is what John Wickerson had in mind when saying tail) – Bruno Le Floch May 23 '13 at 16:18
9

In the spirit of John Wickerson's own answer (no extra packages), but with tail extraction support. The macro \spliceList{<csv list>} defines macros \head and \tail:

\documentclass{article}

% \spliceList{1,2,3} --> \def\head{1} \def\tail{3,4}
% \spliceList{7}     --> \def\head{1} \def\tail{}
\def\spliceList#1{\expandafter\spliceListAux #1,\END}

\def\spliceListAux#1,#2\END{%
  \def\head{#1}
  \ifx\END#2\END
    \def\tail{}% tail is empty
  \else
    % remove trailing comma
    \expandafter\spliceListAuxAux #2\END 
  \fi
}
\def\spliceListAuxAux#1,\END{\def\tail{#1}}

\begin{document}
  \def\johnlist{2,3,5,7}
  \spliceList{\johnlist}
  List: \johnlist{} Head: \head{} Tail: \tail{}\par
  \spliceList{1}
  List: 1 Head: \head{} Tail: \tail{}\par

\end{document}

enter image description here


Supporting \last, which was a late requirement, is not possible with delimited arguments alone, but requires some sort of loop construct.

This version of \spliceList{<csv list>} defines \head, \tail and \last. It is based on the \@for loop construct provided by the LaTeX kernel, so, again, no extra packages are needed:

\documentclass{article}

\makeatletter

% \spliceList{1,2,3} --> \def\head{1} \def\tail{2,3} \def\last{3}
% \spliceList{7}     --> \def\head{7} \def\tail{7} \def\last{7}
% \spliceList{}      --> \let\head=\relax \let\tail=\relax \let\last=\relax 
\def\spliceList#1{%
  \let\head=\relax
  \let\tail=\relax
  \let\last=\relax
  \@for\tmp:=#1\do{%
    % store \tmp in \last (expand once)
    \edef\last{\expandafter\unexpanded\expandafter{\tmp}}%
    % if \head==\relax store \tmp in \head (expand once)
    \ifx\head\relax 
      \edef\head{\expandafter\unexpanded\expandafter{\tmp}}
    \else
      % if \tail==\relax store \tmp in \tail (expand once) 
      \ifx\tail\relax 
        \edef\tail{\expandafter\unexpanded\expandafter{\tmp}}
      % otherwise append ,\tmp to \tail (expand once)
      \else
        \edef\tail{\expandafter\noexpand\tail,\expandafter\unexpanded\expandafter{\tmp}}
      \fi
    \fi
  }
}

\makeatother

\def\testit#1{\spliceList{#1}List: #1 $\rightarrow$ Head: \head{} Tail: \tail{} Last: \last{}\par }

\begin{document}
  \def\johnlist{2,3,5,7}
  \testit{\johnlist}
  \testit{1,2}
  \testit{1}
  \testit{}
\end{document}

enter image description here

  • Lovely answer, thank you Daniel. I was almost there myself, but didn't think to define the AuxAux command to remove that trailing comma. I have updated my question to ask for the last element of a list too, since it's such a closely-related task. Would you care to update your answer to include that? – John Wickerson May 24 '13 at 6:41
  • @JohnWickerson: Done – Daniel May 24 '13 at 15:02
  • You can say \edef\head{\unexpanded\expandafter{\tmp}}. – cjorssen May 24 '13 at 16:32
6

My first thought was to use something like

\def\headOfList#1,#2{#1}

but that doesn't work. For instance, \headOfList{2,3,5,7} gives {2,5,7}. (The #2 matches the 3, not the 3,5,7.) So let's terminate our lists with . and try something like the following.

\def\headOfList#1,#2.{#1}

This struggles when the list has just a single element, because it always looks for a comma after the first element. So, we arrange that when we call \headOfList, we stick a , and a . on the end of the list first. This gives the following result, which is quite good, but still feels like it could be simpler.

\documentclass{article}
\usepackage{tikz}
\def\headOfListAux#1,#2.{#1}
\def\headOfList#1{\expandafter\headOfListAux #1,.}
\begin{document}
  \def\johnlist{2,3,5,7}
  \begin{tikzpicture}
    \def\x{\headOfList\johnlist}
    \node[shape=circle,draw=black] at (\x,0) {\x};
  \end{tikzpicture}
\end{document}
  • 2
    Instead of a ., I'd rather delimit the argument with some undefined control sequence. It works as a good detector if something wrong happens with the arguments: \def\headOfListAux#1,#2\MyUndefined{#1}\def\headOfList#1{\expandafter\headOfListAux#1\MyUndefined} – cjorssen May 23 '13 at 11:31
6

Here I'm using a stringstrings approach. I first convert the commas to spaces (if you wanted to grab arguments other than the first list item, I'm assuming that your data is space free). Then I grab each item from a space-separated list with \getargs. The items of \johnlist are stored in \argi, \argii, \argiii, and \argiv respectively. So I use \argi to grab the first list item. Getting the last argument is only slightly more difficult, in the general case. I build up a \csname using \narg, which contains the total number of arguments.

In the first picture, I grab the first and the last elements of the list. In the second picture I grab elements 2...n.

\documentclass{article}
\usepackage{stringstrings}
\usepackage{tikz}
\usepackage{ifthen}
\begin{document}
  \def\johnlist{2,3,5,7}
  \convertchar[q]{\johnlist}{,}{ }
  \getargs{\thestring}
  \def\argn{\csname arg\romannumeral\narg\endcsname}
  \begin{tikzpicture}
    \node[shape=circle,draw=black] at (\argi,0) {\argi};
    \node[shape=circle,draw=black] at (\argn,0) {\argn};
  \end{tikzpicture}   

\newcounter{index}\setcounter{index}{1}
  \begin{tikzpicture}
    \whiledo{\value{index} < \narg}{%
      \def\argj{\csname arg\roman{index}\endcsname}
      \addtocounter{index}{1}%
      \node[shape=circle,draw=black] at (\argj,0) {\argj};
    }
  \end{tikzpicture}   
\end{document}

enter image description here

  • Thanks for your answer, Steven. I have updated my question to generalise it to getting the tail as well. If you have the time, would you care to address that in your answer too? Thanks again! – John Wickerson May 23 '13 at 15:53
  • @JohnWickerson Done, as edited above. – Steven B. Segletes May 23 '13 at 16:01
  • I think John Wickerson was thinking of the tail as all items except the head (so 3,5,7 in your example). – Bruno Le Floch May 23 '13 at 16:05
  • @BrunoLeFloch Likewise addressed in edit. – Steven B. Segletes May 23 '13 at 16:14
6

The environment (TikZ or otherwise) in which you use this solution doesn't matter. It gives \firstitem, \lastitem, \remainder and \tailoflist.

\usepackage{loops}[2013/05/01]
\makeatletter
% \getHeadAndTailOfList[<parser>]{<list>}
% \getHeadAndTailOfList*[<parser>]{<listcmd>}
\def\getHeadAndTailOfList{\skv@teststopt\g@theadandtailoflist,}
\def\g@theadandtailoflist[#1]#2{%
  \def\firstitem{}\def\lastitem{}\def\remainder{}%
  \skvexpanded{\newforeach\ifskv@tempst*\fi}[parser={#1}]\x in{#2}{%
    \ifnum\foreachitemcount=\@ne
      \def\firstitem{##1}%
    \else
      \ifforeachlastitem
        \def\lastitem{##1}%
      \else
        \skvaddtolist[#1]\remainder{##1}%
      \fi
    \fi
  }%
  \edef\tailoflist{%
    \skvexpandonce\remainder
    \ifx\remainder\@empty\else\ifx\lastitem\@empty\else#1\fi\fi
    \skvexpandonce\lastitem
  }%
}
\makeatother

Tests:

% Default parser is comma (,):
\getHeadAndTailOfList{1,...,10}
% \show\tailoflist % -> {2,3,4,5,6,7,8,9,10}

% Parser is semicolon (;):
\def\alist{1;3;...;12}
\getHeadAndTailOfList*[;]\alist
% \show\tailoflist 
5

Here is an example using lambda-lists:

\documentclass{article}
\usepackage{lambda} % http://ctan.org/pkg/lambda-lists
\begin{document}
\def\mylist{\Listize[a,b,c,d,e]}
\Head\mylist \par
\Unlistize{\Tail\mylist}
\end{document}

returns a and [b,c,d,e].

Last element of list:

\Head{\Reverse\mylist}
  • Nice! I've updated my question to ask for the last element as well, since some people were confusing that with the tail. Would you care to extend your answer to explain how to obtain the last element of a list? – John Wickerson May 24 '13 at 6:47
  • @JohnWickerson: Sure, done. – morbusg May 24 '13 at 7:47
4

My solution is more or less identical to the solution of @Daniel for \head and \tail, but I use recursion to get \last.

\documentclass{article}

\makeatletter
    \def\head#1{\expandafter\@head#1,\@eol}
    \def\@head#1,#2\@eol{#1}
    \def\tail#1{\expandafter\@tail#1,\@eol}
    \def\@tail#1,#2\@eol{\ifx\@eol#2\@eol\relax\else\@@tail#2\@eol\fi}
    \def\@@tail#1,\@eol{#1}
    \def\last#1{\expandafter\@last#1,\@eol}
    \def\@last#1,#2\@eol{\ifx\@eol#2\@eol#1\else\@last#2\@eol\fi}
\makeatother

\begin{document}
    \def\johnlist{2,3,5,7}
    List: \johnlist\ Head: \head\johnlist\ Tail: \tail\johnlist\ Last: \last\johnlist\par
    \def\johnlist{1}
    List: \johnlist\ Head: \head\johnlist\ Tail: \tail\johnlist\ Last: \last\johnlist\par
\end{document}
  • concise and elegant :-) – Daniel May 24 '13 at 17:21

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.