9

Is there a way, with the following restrictions:

  • No external pacakges (i.e., no substr)
  • no conditionals (\ifnum, \ifdefined, etc)

to select a character from a string? i.e.

\CharAt{2}{ABC}   

should yield B. Or at least a word from a delimited parameter? i.e.

\WordAt{2}{A B C} 

should yield B.

The size of the array is fixed, so something like this would work as well:

\WordAt{2}{A}{B}{C}

The emphasis is on making the LaTeX simple, because I am using plastex.

  • 1
    What bounds do you have on the length of the array? Does plastex support delimited arguments, i.e., things like \def\foo#1,{}? Does it support nested definitions such as \def\foo{\def\baz##1{}}? – Bruno Le Floch Oct 24 '13 at 16:35
  • @BrunoLeFloch, the array length can be 2 for now. I added your two examples above and they compile without errors. – Leo Oct 24 '13 at 17:26
  • could you try \def\foo{FOO}\csname foo\endcsname and tell us if it compiles without errors and prints FOO? – user4686 Oct 26 '13 at 12:03
8

The following works if the position number is 9 or less:

\documentclass{article}
\def\ninthofmany#1#2#3#4#5#6#7#8#9{#9\gobbletorelax}
\def\gobbletorelax#1\relax{}
\def\CharAt#1#2{%
  \expandafter\ninthofmany\romannumeral\numexpr(9000-\number#1000)#2\relax}
\begin{document}
\CharAt{3}{abcdefg}
\CharAt{2}{abcdefg}
\CharAt{1}{abcdefg}
\end{document}

\CharAt{3}{abcdefg}, for example, prepends 6 "m"s (roman numeral for 6000) in front of the string abcdefg, and then picks off the 9th character of the result. Using iterations of the same trick, one could extend the limit on the first parameter as far as needed, at the expense of longer and longer code.

One could easily solve this puzzle for arbitrarily large numbers with a few lines of code if we weren't required to avoid \ifnum (and presumably other \ifs).

  • What is the simplest version possible if you assume that the string has length 9 or less? – Leo Oct 26 '13 at 4:00
  • Maybe: \def\CharAt#1#2{\def\numbertoget{#1}\GetIt#2{}{}{}{}{}{}{}{}{}} \def\GetIt#1#2#3#4#5#6#7#8#9{\ifcase\numbertoget\or#1\or#2\or#3\or#4\or#5\or#6\or#7\or#8\or#9\fi} – Dan Nov 8 '13 at 6:45
7

It can only work if you are searching within the first 8 characters of the string, or it can get the 9th if the string is only nine characters long. Spaces are ignored in the string argument. It can also parse word lists, where the first argument is specified as {{word1}{word2}{word3}...}

\documentclass{article}
\def\nextchar#1#2#3#4#5#6#7#8#9\relax{%
  \def\1{#1}%
  \def\2{#2}%
  \def\3{#3}%
  \def\4{#4}%
  \def\5{#5}%
  \def\6{#6}%
  \def\7{#7}%
  \def\8{#8}%
  \def\9{#9}%
}
\newcommand\findchar[2]{%
  \nextchar#1\relax\relax\relax\relax\relax\relax\relax\relax\relax%
  \csname#2\endcsname%
}
\begin{document}
\findchar{abc}{2}
should give a b

\findchar{thisisatest}{7}
gives the same result as
\findchar{this is a test}{7}

\findchar{123456789}{9}
can find the ninth character, since the string is only 9 in length

\findchar{{This}{is}{a}{test}{of}{the}{emergency}{broadcast}{system}}{7}
can be found among word lists too.
\end{document}

enter image description here

  • you could write \expandafter\def\csname1\endcsname{#1}% as \def\1{#1} which would look a bit less obscure. – David Carlisle Oct 24 '13 at 18:07
  • @DavidCarlisle Thanks. That was somewhere in the back of my mind as a possibility, but it slipped away. – Steven B. Segletes Oct 24 '13 at 19:55
  • 1
    \newcommand\findchar[2]{\def\nextchar##1##2##3##4##5##6##7##8##9\relax{###2}\nextchar#1\relax\relax\relax\relax\relax\relax\relax\relax\relax} with no prior definition of \nextchar should also work, if plastex does the right thing with ## within definitions. If we are sure that the strings have at most 9 characters, then delimited arguments can be avoided entirely, writing \newcommand\findchar[2]{\renewcommand\nextchar[9]{###2}\nextchar#1\relax\relax\relax\relax\relax\relax\relax\relax\relax} with whatever prior \newcommand\nextchar{...} you like. – Bruno Le Floch Oct 24 '13 at 23:21
  • 1
    Unfortunately plastex does not seem to handle ## as expected. – Leo Oct 26 '13 at 3:45
  • @BrunoLeFloch see Leo's comment: Unfortunately plastex does not seem to handle ## as expected – Steven B. Segletes Oct 26 '13 at 3:46
5

I looked a bit at the plastex pdf documentation but could not find any concrete explanation of what is acceptable and what is not.

As a rhetorical exercise, here is a macro \NthElt {N}{list} which returns (expandably) the Nth element of the list (spaces gobbled), the list may be a macro expanding to some string, or some other list of braced things; the N has to be an explicit positive integer.

My constraint was not to use any \if but as it has been used in other answers I allowed myself delimited macros and \romannumeral. Use of \romannumeral could be removed entirely; delimited macros are at the heart of the method.

And this is e-TeX as \numexpr is crucially used.

\documentclass{article}

\makeatletter
\def\gobble@     {}%
\def\gobble@i    #1{}%
\def\gobble@ii   #1#2{}%
\def\gobble@iii  #1#2#3{}%
\def\gobble@iv   #1#2#3#4{}%
\def\gobble@v    #1#2#3#4#5{}%
\def\gobble@vi   #1#2#3#4#5#6{}%
\def\gobble@vii  #1#2#3#4#5#6#7{}%

\def\gobtoundef    #1\undef {}% 
\def\gobtonine     #19{}%
%\def\FirstOfTwo   #1#2{#1}%   here, LaTeX: just use \@firstoftwo
%\def\ThirdOfThree #1#2#3{#3}% here, LaTeX: just use \@thirdofthree

% EXPANDABLY RECOVERS THE #1th ITEM
% #1 MUST BE AN EXPLICIT POSITIVE INTEGER  (not a count or not even a macro)
%     
\def\NthElt #1#2%
{%
    \romannumeral0\expandafter\NthElta\expandafter {\romannumeral-`0#2}{#1}%
}%
\def\NthElta #1#2%
{%
    \NthEltb {#2}#1\undef\undef\undef\undef\undef\undef\undef\undef\UNDEF
}%
\def\NthEltb #1%
{%
    \IfMoreThanEight #1\undef\@firstoftwo {\NthEltc}{\NthEltGetIt}%
    {#1}%
}%
\def\IfMoreThanEight #1#2%
{% 
    \gobtoundef #2\PerhapsAtMostEight #1%
}%
\def\PerhapsAtMostEight #1%
{%
    \gobtonine #1\gobble@ii 9\@thirdofthree
}%
\def\NthEltc #1#2#3#4#5#6#7#8#9%
{%
    \gobtoundef #9\NthEltSilentEnd\undef
    \expandafter\NthEltb\expandafter{\the\numexpr #1-8\relax}%
}%
\def\NthEltSilentEnd #1\UNDEF { }%  space stops the \romannumeral0
\def\NthEltGetIt #1%
{%
    \expandafter\expandafter\expandafter\NthEltFinish
    \csname gobble@\romannumeral\numexpr#1-1\endcsname
}%
\def\NthEltFinish #1#2\UNDEF  {\gobtoundef #1\expandafter\space
                                             \gobble@iii\undef\space #1}% 
% space stops the \romannumeral0
\makeatother

\begin{document}    
\NthElt {1}{ABCDEFGHIJKLMNOPQRSTUVWXYZ}

\NthElt {7}{ABCDEFGHIJKLMNOPQRSTUVWXYZ}

\NthElt {26}{ABCDEFGHIJKLMNOPQRSTUVWXYZ}

+++\NthElt {27}{ABCDEFGHIJKLMNOPQRSTUVWXYZ}+++

% 0 is not legal as index, but acts as if 1.
+++\NthElt {0}{ABCDEFGHIJKLMNOPQRSTUVWXYZ}+++

\newcount\cnta
\loop
   \advance\cnta 1
      \expandafter\NthElt\expandafter{\the\cnta}{ABCDEFGHIJKLMNOPQRSTUVWXYZ}.%
   \ifnum\cnta < 30
\repeat

\edef\z {\NthElt
  {37}{ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJ{THIS IS ME!}LMNOPQRSTUVWXYZ}}
\texttt{\meaning\z}

\end{document}

nth element

  • in \NthElta replace \NthEltb {#2} by \expandafter\NthEltb\expandafter{\the\numexpr #2} and then the index parameter can be count register or a macro expanding to a positive integer. Don't know why I left that out. The original in a package of mine from which this is extracted and adapted (had to get rid of \ifnum...) also tested for negative index, in which case it returns from the tail of the list, or zero index, in which case it returns the length of the list. – user4686 Oct 25 '13 at 19:01
  • The \relax in \expandafter{\the\numexpr #1-8\relax} is superfluous and should be removed. In some other macros of this style sometimes the stuff would be unpacked before action of \expandafter chain, and then the \relax is necessary, so as a rule I always have them when first write the code, then I remove if ok. Can be removed here. – user4686 Oct 25 '13 at 19:09

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.