9

This is some code.

\documentclass{article}

\usepackage{xstring}

\begin{document}

\def\xloop#1{%
  \ifx\relax#1
    \else

     **\IFInteger{#1}{first integer is here.}** % need to be modified

      \expandafter\xloop% 
  \fi}

\def\test#1{\xloop#1\relax}

\test{abc3de5f}

\end{document}

5 Answers 5

8

On the original question of how to terminate your loop:

In case of your \xloop you have a terminating \relax. Provided that your input never contains a \relax token (which is already a prerequisite of your own loop) you can simply terminate your loop by swallowing everything up to that \relax, you just have to pay attention, since you're swallowing a \fi in the process you'll need to reinsert one. The following does that (but is then equivalent to the, imho, more clean expl3 based solution of my other answer):

\documentclass{article}

\usepackage{xstring}

\begin{document}

\def\xloop#1{%
  \ifx\relax#1%
    \else
     \IfInteger{#1}{\xloopDone}{}%
      \expandafter\xloop
  \fi}
\def\xloopDone#1\relax{\fi first integer was here}

\def\test#1{\xloop#1\relax}

\test{abc3de5f}

\end{document}

Yields first integer was here and nothing else.

10

This answer relies on the fact that the digits 0-9 fall sequentially on the ASCII chart between slots 48 and 57, inclusive. The slot just prior to 0 contains / and the slot following 9 contains :.

enter image description here

We make use of TeX syntax that a grave accent (backtick) prior to a symbol produces the slot number (ASCII value) for the symbol. So I set up two \ifnum tests...the first looks to see if the slot of the current symbol is beyond the slot of /, and the second test checks if the slot of the current symbol is prior to the slot of :.

If both these conditions are true, the slot of the symbol falls in the range of the integer slots. Otherwise, it falls outside that range.

\documentclass{article}

\def\xloop#1{%
  \ifx\relax#1
    \else
%
      \ifnum`#1>`/ 
        \ifnum`#1<`:
           \def\next{#1 is first integer\\}%
        \else
          \def\next{#1 is not an integer\\\expandafter\xloop}
        \fi
      \else
        \def\next{#1 is not an integer\\\expandafter\xloop}
      \fi
      \next% 
  \fi}

\def\test#1{\xloop#1\relax}
\begin{document}
\test{abc3de5f}
\end{document}

enter image description here

Skillmon suggests a faster alternative which, as he notes, is equally robust. In this case the comparison 1<1\noexpand#1 will prove false, unless #1 is a numerical digit.

\documentclass{article}

\def\xloop#1{%
  \ifx\relax#1
    \else
%
      \ifnum1<1\noexpand#1 
         \def\next{#1 is first integer\\}%
      \else
        \def\next{#1 is not an integer\\\expandafter\xloop}
      \fi
      \next% 
  \fi}

\def\test#1{\xloop#1\relax}
\begin{document}
\test{abc3de5f}
\end{document}
7
  • 1
    could you explain a little bit what this does? Commented Oct 1, 2024 at 4:37
  • this can check if a variable name contain number. Commented Oct 1, 2024 at 5:17
  • 1
    Though your test is robust, it is considerably slower than the equally robust check using \ifnum1<1\noexpand#1. Commented Oct 1, 2024 at 19:31
  • 1
    @xcn In any \if-like expression, the grave accent before a character indicates that the character-slot number of that character is to be given (as an integer), rather than the character itself. Since the slot (ASCII code) of a / is 47, the test is to determine if the slot of the given character #1 is greater than 47. The second such test determines if the slot of the given character is less than 58 (the slot of :). If you look at an ASCII chart, the only such character slots for #1 that qualify are the digits 0 through 9. Commented Jan 19 at 16:57
  • 1
    thanks @StevenB.Segletes what the second grave accent behind #1 indicate ? As a pair of accent is used around #1 . oh. I understand, it's for /. Commented Jan 19 at 23:12
9

Here is an expandable solution using TeX primitives only:

\def\xloop#1{%
   \ifx\relax#1\else
      \ifnum 2<1#1 \loopE \else non-integer:#1 \fi
      \expandafter \xloop
   \fi
}
\def\loopE #1\relax{\fi\fi}

\xloop abc3de0f\relax

\bye

The test if #1 is an integer is done by \ifnum. There are two situations:

\ifnum 2<1x \loopE \else non-integer:x\fi

this test is false because 2<1 is not true. The else-text is procesed.

\ifnum 2<10 \loopE \else non-integer:0\fi

this test is true because 2<10 (or 11, or 12, etc.) is true. The macro \loopE does final work: ignores the next text to \relax and closes two opened \if branches.

0
5

Why just doing only digits?

Here \defineclass provides a form of \str_case:nnF, so the current item in the loop can be tested for being in the class or not.

With \loopbreak you can stop the loop. See the last examples.

\documentclass{article}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\stringloop}{mmmmm}
 {% #1 = string to test
  % #2 = class name (CN)
  % #3 = what to do if the item is in the class (YES)
  % #4 = what to do if the item is not in the class (NO}
  % #5 = what to do at the end if not stopped (END)
  \xcn_stringloop:ennnn {\tl_to_str:n {#1}} {#2} {#3} {#4} {#5}
 }

\cs_new:Nn \xcn_stringloop:nnnnn
 {
  \__xcn_stringloop:w {#2} {#3} {#4} {#5} #1 \q_nil
 }
\cs_generate_variant:Nn \xcn_stringloop:nnnnn {e}

\cs_new:Npn \__xcn_stringloop:w #1 #2 #3 #4 #5
 {% #1 = CN, #2 = YES, #3 = NO, #4 = END, #5 = current item in string
  \quark_if_nil:nTF {#5}
   {
    #4
   }
   {
    \__xcn_stringloop_item:nnnnn {#1} {#5} {#2} {#3} {#4}
   }
 }
\cs_new:Nn \__xcn_stringloop_item:nnnnn
 {% #1 = CN, #2 = current item in string, #3 = YES, #4 = NO, #5 = END
  \use:c {__xcn_stringloop_class_#1:nTF} {#2} {#3} {#4}
  \__xcn_stringloop:w {#1} {#3} {#4} {#5}
 }

\NewDocumentCommand{\defineclass}{mm}
 {% #1 = class name, #2 = members
  \cs_new:ce {__xcn_stringloop_class_#1:nTF}
   {
    \exp_not:N \str_case:nnF {##1}
     { \tl_map_function:nN {#2} \__xcn_stringloop_make_case:n }
     { ##3 }
   }
 }
\cs_new:Nn \__xcn_stringloop_make_case:n { {#1}{##2} }

\NewExpandableDocumentCommand{\loopbreak}{m}
 {
  #1 \__xcn_stringloop_break:w
 }
\cs_new:Npn \__xcn_stringloop_break:w #1 \q_nil {}

\NewExpandableDocumentCommand{\checkforTF}{mmmm}
 {
  \xcn_stringloop:ennnn {\tl_to_str:n {#1}} {#2} {\loopbreak{#3}} {} {#4}  
 }

\ExplSyntaxOff

\defineclass{digits}{0123456789}
\defineclass{unknowns}{xyz}

\begin{document}

\raggedright

\stringloop{abc3def5g}{digits}{DIGIT }{NONDIGIT }{END}

\bigskip

\stringloop{abc3def5g}{digits}{\loopbreak{DIGIT, STOP}}{NONDIGIT }{END}

\bigskip

\checkforTF{abc3def5g}{digits}{There's a digit}{There's no digit}

\bigskip

\checkforTF{abcdefg}{digits}{There's a digit}{There's no digit}

\bigskip

\edef\test{\checkforTF{abc3def5g}{digits}{There's a digit}{There's no digit}}
\texttt{\meaning\test}

\bigskip

\stringloop{abxcy}{unknowns}{(unknown)}{(parameter)}{}

\bigskip

\stringloop{abxc}{unknowns}{\loopbreak{(unknown)}}{(parameter)}{end}

\bigskip

\stringloop{abc}{unknowns}{\loopbreak{(unknown)}}{(parameter)}{end}

\end{document}

enter image description here

4

If this is only about checking whether there was a number contained, the following is a rather fast approach for that:

\documentclass{article}

\ExplSyntaxOn
\NewExpandableDocumentCommand\ifcontainsnum{mmm}
  { \xcn_if_contains_num:nTF {#1} {#2} {#3} }
\prg_new_conditional:Npnn \xcn_if_contains_num:n #1 { TF, T, F, p }
  {
    \str_map_function:nN {#1} \__xcn_if_contains_num:n
    \prg_return_false:
  }
\cs_new:Npn \__xcn_if_contains_num:n #1
  { \xcn_if_digit:nT {#1} { \str_map_break:n { \use_i:nn \prg_return_true: } } }
\prg_new_conditional:Npnn \xcn_if_digit:n #1 { TF, T, F, p }
  {
    \if_int_compare:w 1 < 1\exp_not:N#1 \exp_stop_f:
      \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }
\ExplSyntaxOff

\begin{document}
\ifcontainsnum{abc}{yes}{no}

\ifcontainsnum{abc1}{yes}{no}

\ifcontainsnum{ab{c1}}{yes}{no}
\end{document}

This yields no, yes, and yes.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.