3

I need to (1) count digits in a number (or count letters in a word), and (2) retrieve any one of those digits (or any one of those letters). Also, there are conditions to be met: expl3 should not be used as well as packages not authored/maintained by LaTeX Team.

\def\zCountDigits#1%
  {% COUNT DIGITS OF #1
  }
\def\zGetDigit#1#2%
  {% GET DIGIT OF #1 AT INDEX #2
  }
bp2017
  • 3,756
  • 1
  • 13
  • 33
  • 2
    I understand that you don't want to pull in expl3 for that, but what's the point of not using packages not maintained by the LaTeX team? For example, xstring provides both \StrLen for the length of a string and \StrChar to get the n-th character. – siracusa Jul 27 '19 at 04:14
  • 2
    expl3 is maintained by the LaTeX Team. expl3 is just a (really, really large) bunch of TeX macros written with a funny catcode setting. The macros you want are \tl_count:n and \tl_item:nn (or \str...): you can always copy them and remove the _ and :. Will it or will it not be expl3 code then? Your requirements seem a bit pointless to me... – Phelype Oleinik Jul 27 '19 at 06:12
  • 1
    @PhelypeOleinik I wholeheartedly understand anyone who doesn't want to learn what is (or at least looks like) essentially a new language to solve some LaTeX problem. (Also, expl3 should not be used as well as packages not authored/maintained by LaTeX Team sounds like OP is perfectly aware that expl3 is maintained by the LaTeX team.) – sgf Jul 27 '19 at 19:54

2 Answers2

6

One can always take existing code and re-work. Here, I've used some ideas from expl3 but slightly simplified (at the cost of a little robustness)

\def\zCountDigits#1{%
  \number\numexpr0\zCountDigitsAux#1\zCountDigitsEnd\relax
}
\def\zCountDigitsAux#1{%
  \ifx\zCountDigitsEnd#1\else+1\expandafter\zCountDigitsAux\fi
}
\def\zCountDigitsEnd{\zCountDigitsEnd}

\zCountDigits{}
\zCountDigits{123}
\zCountDigits{abC}

\def\zGetDigit#1#2{%
  \zGetDigitLoop{1}{#2}#1\zGetDigitEnd
}
\def\zGetDigitLoop#1#2#3{%
  \ifx\zGetDigitEnd#3\else
    \ifnum #1 = #2 %
      \expandafter\expandafter\expandafter\zGetDigitCleanup
        \expandafter\expandafter\expandafter#3%
    \else
      \zGetDigitLoopStep{#1}{#2}%
    \fi
  \fi
}
\def\zGetDigitCleanup#1#2\zGetDigitEnd{#1}
\def\zGetDigitLoopStep#1#2\fi\fi{\fi\fi
  \expandafter\zGetDigitLoop\expandafter{\number\numexpr#1 + 1\relax}{#2}%
}
\def\zGetDigitEnd{\zGetDigitEnd}

\zGetDigit{abc}{1}
\zGetDigit{abc}{3}
\zGetDigit{abc}{4}
\zGetDigit{abc}{-2}

\bye

(I'm not 100% from the spec on whether you want space handling, brace groups preserved, etc.: I've gone for the simple approach, but they could be included).


The way that the loop works is that \zCountDigitsAux grabs one token at a time: if you look at the set up, there are no braces around #1 in \zCountDigits. Thus \ifx\zCountDigitsEnd#1 is comparing exactly one token to the end marker. I've used a private 'end of loop' token rather than say \relax. That's to ensure that we can still count an input sequence that includes \relax: the counting approach here is valid not only for character tokens but also macros, \chardef tokens, etc.

Assuming the loop has not finished, we insert +1 then need to close the conditional. The \zCountDigitsAux macro needs to see what is next in the input stream, and we need to avoid opening more and more conditional levels. Hence I use \expandafter\zCountDigitsAux\fi. This expands the \fi, ending the conditional, before \zCountDigitsAux gets expanded. (Without this, \zCountDigitsAux would grab the \fi, and we'd run out of stack.) There's no parameter passed to \zCountDigitsAux here as the rest of the original argument to \zCountDigits is still in the input stream.

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
  • 1
    @bp2017 See my edit – Joseph Wright Jul 28 '19 at 07:49
  • 1
    @bp2017 Well known trick: it's a 'quark'. If you use say {}, you can get a hit against a token with a different name but the same meaning. With the self-definition that can't happen. – Joseph Wright Jul 28 '19 at 16:58
  • 1
    @bp2017 I'm making sure I end the conditionals then deal with #3. As I don't know how many tokens it is, I use a delimited argument to 'clear up' the \fi then re-insert them. I could use any marker, but as it makes the conditional clearer to read, normally one sticks to the required number of \fi. – Joseph Wright Jul 28 '19 at 17:06
5

The following solution uses no packages at all and hence cannot run afoul of the stricture against the use of packages "not authored/maintained by LaTeX Team". (Aside: I have no idea who the members of "LaTeX Team" are -- or even if there is such a thing.)

Since you use \def rather than \newcommand in your example, I take it that you're interested primarily in a "plain TeX" solution. The code shown below therefore uses LuaTeX, not LuaLaTeX. The code provides two macros, \zCountChars and \zGetChar. The macros use the "primitive" command \directlua and the built-in LuaTeX functions tex.sprint, string.len, and string.sub.

enter image description here

\def\zCountChars#1{\directlua{tex.sprint(string.len("#1"))}}
\def\zGetChar#1#2{\directlua{tex.sprint(string.sub("#1",#2,#2))}}

\zCountChars{1066}, \zCountChars{123abc}.

\zGetChar{6789}{2}, \zGetChar{123abc}{-2}.
\bye

Note that it's possible to use a negative number as the second argument of \zGetChar. In such cases, the counting starts from the end of the string/number rather than from the beginning.

Mico
  • 506,678
  • 1
    +1: certainly the most efficient way :-) There is a LaTeX Team, and I'm a member of it (so they say :-). And even so OP's requirements are a bit weird... – Phelype Oleinik Jul 27 '19 at 06:18
  • 4
    @Mico You never looked at the photos of our heroes? ;-) – frougon Jul 27 '19 at 07:06
  • 1
    @PhelypeOleinik - Many thanks for this pointer. I knew there was something called the LaTeX Project, but I wasn't aware of the existence of an associated entity called the LaTeX Team. The team members sure look like an all-star cast! And, of course, my thanks to you for volunteering to be a member of this illustrious team. – Mico Jul 27 '19 at 07:25