4

I'm looking for a solution that checks if "alien characters" are contained in a string.

I found Check if a string contains a given character, which looks for one character only. However, I can't adopt this to my needs.

I.E.: Let the main string be asdf.

\check{asdf}{asdfgh} should return true, because my main string is a true subset of my string asdfgh which contains non-alien characters.

\check{asdf}{hgfdsa} should return true, too. Same as \check{adfs}{hdagsf} - Sequence of chars doesn't matter, only occurence.

\check{asdf}{asdghj} should return false, because the f character in main string is an alien as it is not in asdghj.

Any ideas welcome.

2 Answers2

9

You can test each item in the first string for being in the second string; at any “non match” stop and return false.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\checkTF}{mmmm}
 {
  \stringchecker_if_string_in:nnTF { #1 } { #2 } { #3 } { #4 }
 }

\bool_new:N \l__stringchecker_temp_bool

\prg_new_protected_conditional:Nnn \stringchecker_if_string_in:nn { T, F, TF }
 {
  % set the temporary boolean to true; it will remain such
  % unless a nonmatch will happen
  \bool_set_true:N \l__stringchecker_temp_bool
  % map over the substring to find
  \str_map_inline:nn { #1 }
   {
    % do nothing if there is a match; ##1 is the current item, #2 the string to search in
    \str_if_in:nnF { #2 } { ##1 }
     {
      % a nonmatch breaks the mapping and sets the boolean to false
      \str_map_break:n { \bool_set_false:N \l__stringchecker_temp_bool }
     }
   }
  \bool_if:NTF \l__stringchecker_temp_bool
   {% there was no nonmatch, return true
    \prg_return_true:
   }
   {% a nonmatch was found, return false
    \prg_return_false:
   }
 }
\ExplSyntaxOff

\begin{document}

\checkTF{asdf}{asdfgh}{TRUE}{FALSE}

\checkTF{asdf}{hgfdsa}{TRUE}{FALSE}

\checkTF{asdf}{asdghj}{TRUE}{FALSE}

\checkTF{}{asdghj}{TRUE}{FALSE}

\checkTF{a}{}{TRUE}{FALSE}

\end{document}

enter image description here

A slightly more efficient version that absorbs one token at a time from the first argument.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\checkTF}{mmmm}
 {
  \stringchecker_if_string_in:nnTF { #1 } { #2 } { #3 } { #4 }
 }

\prg_new_protected_conditional:Nnn \stringchecker_if_string_in:nn { T, F, TF }
 {
  \__stringchecker_check:nN { #2 } #1 \q_nil
 }

\cs_new_protected:Nn \__stringchecker_check:nN
 {
  \quark_if_nil:NTF #2
   {
    \prg_return_true:
   }
   {
    \str_if_in:nnTF { #1 } { #2 }
     {
      \__stringchecker_check:nN { #1 }
     }
     {
      \__stringchecker_stop:w
     }
   }
 }
\cs_new:Npn \__stringchecker_stop:w #1 \q_nil
 {
  \prg_return_false:
 }
\ExplSyntaxOff

\begin{document}

\checkTF{asdf}{asdfgh}{TRUE}{FALSE}

\checkTF{asdf}{hgfdsa}{TRUE}{FALSE}

\checkTF{asdf}{asdghj}{TRUE}{FALSE}

\checkTF{}{asdghj}{TRUE}{FALSE}

\checkTF{a}{}{TRUE}{FALSE}

\end{document}
egreg
  • 1,121,712
6
\documentclass{article}

\usepackage{xinttools}

\makeatletter
\newcommand\mycheck[2]{%
   \xintFor*##1in{#1}:
   {%
      \in@{##1}{#2}%
      \unless\ifin@\expandafter\xintBreakFor\fi
   }%
   \ifin@\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\makeatother

\newcommand\test[2]{#1 in #2 ? \mycheck{#1}{#2}{TRUE}{FALSE}}
\begin{document}

\test{asdf}{asdfgh}

\test{asdf}{hgfdsa}

\test{asdf}{asdghj}
\end{document}

enter image description here

  • 1
    @StringChecker completely forgot to say that with active characters you must use braces like this \test{{é}{ç}{à}}{pàqçzé}, \test{{é}{ç}{à}}{pàqzé}, with pdflatex (not with lualatex, xelatex). This applies to Babel shorhands too, naturally. And, with braces you can apply \mycheck also to macros, even undefined \mycheck{{\error}{\undefined}}{\a\b\error\c\undefined\d}{TRUE}{FALSE} (can't use \test here which will try to "print" #1={\error}{\undefined} and #2 same problem) –  Jan 13 '19 at 18:14
  • reinserting my comment which can be useful for passers-bye that the case of empty list of characters as first argument is undecided (OP said it did not matter). Simply add \in@true or \in@false before the \xintFor* loop depending on what is expected in case of empty list replacing the asdf of example. –  Jan 14 '19 at 10:32