13

This question is related to Extract first & last characters of macro argument?.

It seems that if, besides getting first and last tokens, one wants to accumulate the rest of the tokens, the solution is nontrivial. My solution appears convoluted. I wonder if David Carlisle can instantly pull a neater one out of his pocket. Full expansion of tokens should be avoided because they may contain undefined controls.

\documentclass{article}
\makeatletter
\usepackage{catoptions}

\def\fl#1{\fl@i#1\@nil\@nil\@nnil}
\def\fl@i#1#2#3\@nnil{%
  \def\rest{}\def\last{}%
  \edef\first{\ifstrcmpTF{#1}\@nil{}{\unexpanded{#1}}}%
  \edef\reserved@b{\unexpanded{#3}}%
  \ifcsemptyTF\reserved@b{%
    \edef\last{\ifstrcmpTF{#2}\@nil{}{\unexpanded{#2}}}%
  }{%
    \ifcseqTF\reserved@b\@nnil{%
      \edef\last{\ifstrcmpTF{#2}\@nil{}{\unexpanded{#2}}}%
    }{%
      \edef\last{\unexpanded{#2}}%
      \fl@ii#3\@nnil
    }%
  }%
  \ifx\last\rest\def\rest{}\fi
}
\def\fl@ii#1#2#3\@nnil{%
  \ifstrcmpTF{#1}\@nil{}{%
    \ifstrcmpTF{#2}\@nil{%
      \edef\rest{\expandcsonce\last\expandcsonce\rest}%
      \edef\last{\unexpanded{#1}}%
    }{%
      \edef\rest{\expandcsonce\rest\unexpanded{#1}}%
      \fl@ii#2#3\@nnil
    }%
  }%
}

\def\x{\string\x}
\def\y#1{\fl{#1}\immediate\write20{Given token
  [\iflacus#1\dolacus empty/null\else#1\fi]
  ^^J[\first][\last][\rest]}
}
\immediate\write20{[first][last][rest]}
\y{}
\y{\x}
\y{1\x2}
\y{123}
\y{1234\x}

\begin{document}
x
\end{document} 
Ahmed Musa
  • 11,742
  • 1
    Hmph that sounds like a challenge – David Carlisle Feb 03 '12 at 21:46
  • 11
    You have asked eight questions, but still accepted no answer. Accepting one of the answers provided is not mandatory, but it would be better if you reconsidered them, as people puts their efforts in answering your questions and probably some of the answers did help you. – egreg Feb 03 '12 at 22:25
  • 2
    Can your token list contain spaces? Can it contain brace groups? What should be returned if the token list starts or ends with a space or group? – Bruno Le Floch Feb 03 '12 at 23:01
  • @AhmedMusa (egreg didn't get notified: you need to write @egreg), you probably only voted those answers up. To accept an answer (only one accepted answer per question), click on the green(?) checkmark below the arrows used for voting. By the way, here, feel free to accept a non-LaTeX3 answer if those were more helpful. – Bruno Le Floch Feb 04 '12 at 23:16

5 Answers5

14

Here is a solution which takes a time linear in the size of the token list, faster than egreg's method. It does not preserve spaces nor brace groups.

The idea is to turn the token list into a sequence using \seq_set_split:Nnn with an empty "delimiter" argument (to split between every token), then use the functions \seq_pop_left:NN and \seq_pop_right:NN to extract (and remove) the first and last items from the sequence. Finally, the sequence holds the contents of the token list without its first and last elements; we need to convert back to a token list: this is done within an \edef (\tl_set:Nx), applying \unexpanded (\exp_not:n) to each item in the sequence, to prevent expansion within this expanding assignment.

\RequirePackage{expl3,xparse}
\ExplSyntaxOn
\seq_new:N \l_your_seq
\DeclareDocumentCommand{\fl}{m}
  {
    \seq_set_split:Nnn \l_your_seq { } {#1}
    \seq_pop_left:NN \l_your_seq \first
    \seq_pop_right:NN \l_your_seq \last
    \tl_set:Nx \middle { \seq_map_function:NN \l_your_seq \exp_not:n }
  }
\ExplSyntaxOff
  • 1
    Bruno: This is great, but what shall some of us who have somewhat refused to learn LaTeX3 do? – Ahmed Musa Feb 04 '12 at 23:05
  • 1
    @Ahmed: (1) code your own or (2) use that code, it works. Seriously, life is short, I'm not going to rewrite everything from scratch every time. – Bruno Le Floch Feb 04 '12 at 23:13
  • 1
    Bruno: I sometimes rewrite a working code. I've learnt a lot from reinventing the wheel. Life is short, but certainly not for learning. BTW, why does this site drop '@name'? – Ahmed Musa Feb 05 '12 at 00:08
  • 1
    @AhmedMusa That's a subtlety of the site: when only two people including the post owner (here, me) dialogue in comments, the @<post owner> is removed, since the post owner would be notified anyways. – Bruno Le Floch Feb 05 '12 at 00:12
10

The LaTeX3 infrastructure can be used for this: it provides \tl_range:nnn for grabbing tokens from a token list.

At the end, the token list variables \l_fl_first_tl, \l_fl_last_tl and \l_fl_rest_tl will contain the required elements. If the token list has length one, the rest and the last elements are empty.

\documentclass[a4paper]{article}

\ExplSyntaxOn \NewDocumentCommand{\fl}{m} { \tl_set:Nx \l_fl_first_tl { \tl_range:nnn { #1 } { 1 } { 1 } } \tl_set:Nx \l_fl_rest_tl { \tl_range:nnn { #1 } { 2 } { -2 } } \int_compare:nT { \tl_count:n { #1 } > 1 } { \tl_set:Nx \l_fl_last_tl { \tl_range:nnn { #1 } { -1 } { -1 } } } % now show the result as [first] [last] [rest] \iow_term:x { === \tl_to_str:n { #1 } === } \iow_term:x { [\tl_to_str:N \l_fl_first_tl] [\tl_to_str:N \l_fl_last_tl] [\tl_to_str:N \l_fl_rest_tl] } } \tl_new:N \l_fl_first_tl \tl_new:N \l_fl_rest_tl \tl_new:N \l_fl_last_tl \ExplSyntaxOff

\def\x{aaaa} \fl{\x} \fl{1\x2} \fl{123} \fl{1234\x} \fl{1234{\x\x}}

\stop

The result is

===\x ===
[\x ][][]
===1\x 2===
[1][2][\x ]
===123===
[1][3][2]
===1234\x ===
[1][\x ][234]
===1234{\x \x }===
[1][{\x \x }][234]
egreg
  • 1,121,712
8

This code doesn't pretend to be the best solution, but it sure is shorter. The macros read the input one token at a time. Non-marginal tokens are appended to \middle. Handling edge cases is left as an exercise. And hail \expandafter!

\documentclass{article}

\makeatletter
\def\split#1{%
  \def\middle{}%
  \def\last{}%
  \let\next=\split@next
  \split@#1\@@end}
\def\split@#1{%
  \def\first{#1}%
  \split@next}
\def\split@next#1{%
  \ifx#1\@@end
    \let\next=\relax
  \else
    \expandafter\expandafter\expandafter\def
    \expandafter\expandafter\expandafter\middle
    \expandafter\expandafter\expandafter{\expandafter\middle\last}%
    \def\last{#1}%
  \fi
  \next}
\makeatother

\begin{document}

\split{12345}
\first,\middle,\last

\end{document}
Andrey Vihrov
  • 22,325
6

Had to slip away, @andrey-vihrov did something similar to I would probably have done, so I thought I'd do something different, this one just works by expansion only, but gets what I hope is the intended answer:

[][][]
[\x ][][]
[1][\x ][2]
[1][2][3]
[1][234][\x ]
[1][234][\x \x ]

plain TeX:

\def\fl#1{%
\toks0\expandafter\expandafter\expandafter{%
\expandafter\fleat\romannumeral`\Q\flx#1\relax\flx}%
\immediate\write30{\the\toks0}}

\def\fla#1#2{#1}
\def\flb#1#2{#2}
\def\fleat#1[{[}

\def\flx#1#2\flx{%
  \ifx\relax#1%
  \expandafter\fla\else\expandafter\flb
  \fi
   {[][][]}{%
  \ifx\relax#2%
  \expandafter\fla\else\expandafter\flb
  \fi
   {[#1][][]}{\fly#2\flx{}{#1}}}}

\def\fly#1#2\flx#3#4{%
  \ifx\relax#2%
  \expandafter\fla\else\expandafter\flb
   \fi
   {[#4][#3][#1]}{\fly#2\flx{#3#1}{#4}}}





\def\x{aaaa}
\fl{}
\fl{\x}
\fl{1\x2}
\fl{123}
\fl{1234\x}
\fl{1234{\x\x}}

\end
David Carlisle
  • 757,742
0

Here's an approach using a token cycle.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{tokcycle}
\def\x{\string\x}
\newcounter{tokcount}
\newcommand\testnext{\stepcounter{tokcount}\tcpeek\zzz}
\def\z#1{\ifx\empty#1\empty[][][]\else
  \setcounter{tokcount}{0}\zz#1\endzz\fi}
\def\zz#1#2\endzz{[#1]\tokcycle
  {\testnext\tctestifx{\empty\zzz}{[##1]}{\addcytoks{##1}}}
  {\testnext\tctestifx{\empty\zzz}{[##1]}{\addcytoks{##1}}}
  {\testnext\tctestifx{\empty\zzz}{[##1]}{\addcytoks{##1}}}
  {\testnext\tctestifx{\empty\zzz}{[##1]}{\addcytoks{##1}}}
  {#2}%
  \ifnum\thetokcount=0 []\fi
  \expandafter[\the\cytoks]%
}
\begin{document}
[First][Last][Mid]

\z{}

\z{\x}

\z{12}

\z{1\x3}

\z{123}

\z{12 }

\z{1 3}

\z{1234\x}

\z{1234{\x\x}}

\z{1234{\x\x}6} Detokenized Mid: \detokenize\expandafter{\the\cytoks} \end{document}

enter image description here