8

I need to perform a string comparison (string equality). If the macro is non-expandable (e.g. contains \def or \let), it causes problems in the place where it is used.

I tried using \ifthenelse from the package with the same name, but it would seem it is not expandable (the same problems as when I use \def in the macro occur).

\ifthenelse{\equal{a}{b}}{if-case}{else-case}

How can I test the equality of two strings (#1 and a constant, for that matter) in an expandable way?

  • package xstring – touhami Apr 26 '16 at 17:37
  • @touhami the IfStrEq command is not expandable. From the xstring docs: "The macros of this package are not purely expandable". I tried it and it produces the same problem as ifthenelse. – Georges Dupéron Apr 26 '16 at 17:44
  • Sorry I don't know, I just thinked it can be helpfull. – touhami Apr 26 '16 at 18:22
7

You can use expl3; the \strcompare macro is just a user level version of the expandable internal function \str_if_eq:nnTF.

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn
\cs_new_eq:NN \strcompare \str_if_eq:nnTF
\ExplSyntaxOff

\begin{document}

start\par
\strcompare{abc}{def}{OOPS}{OK}\par
\strcompare{abc}{abcd}{OOPS}{OK}\par
\strcompare{abcd}{abc}{OOPS}{OK}\par
\strcompare{a}{}{OOPS}{OK}\par
\strcompare{}{a}{OOPS}{OK}\par
end

\medskip

start\par
\strcompare{abc}{abc}{OK}{OOPS}\par
\strcompare{}{}{OK}{OOPS}\par
end

\end{document}

enter image description here

3

I've read that with \usepackage{pdftexcmds}, you can use \pdf@strcmp but haven't checked if it works in this case.

Alternatively, the code below defines \expandable@if@equal{str1}{str2}{if-case}{else-case}. As @JavierBezos noted in the comments, spaces are not taken into account (so "a b" = "ab"), and a version which takes them into account would be harder to achieve (if at all possible?). It can be used as follows:

\makeatletter
start%
\expandable@if@equal{abc}{def}{OOPS}{OK}%
\expandable@if@equal{abc}{abcd}{OOPS}{OK}%
\expandable@if@equal{abcd}{abc}{OOPS}{OK}%
\expandable@if@equal{a}{}{OOPS}{OK}%
\expandable@if@equal{}{a}{OOPS}{OK}%
end

start%
\expandable@if@equal{abc}{abc}{OK}{OOPS}%
\expandable@if@equal{}{}{OK}{OOPS}%
end
\makeatother

Which prints (notice that no extraneous space gets inserted):

startOKOKOKOKOKend
startOKOKend

Here's the definition of \expandable@if@equal:

\makeatletter

%         #1       #2          #3    #4                                  \sepend
% Params: {if-case}{else-case} \sepr \sepr                               \sepend
% Or:     {if-case}{else-case} B     right-…(may be *nothing*)\sepr\sepr \sepend
\def\@expandable@if@equal@empty#1#2#3#4\sepend{%
  \ifx#3\sepr% #3right… = Empty string, a.k.a. "\sepr"
    % First case in the signature above (#3=\sepr, #4=sepr)
    #1% The strings are both empty, and therefore equal. Execute #1.
  \else%
    % Second case in the signature above (#3=one char, #4=right…\sepr\sepr)
    #2% The right string is longer (there's at least one more character), so they are different. Execute #2.
  \fi%
}%
%         #1       #2         #3          #4       \sepl #5    #6                        \sepr #7        \sepend
% Params: {if-case}{else-case}{left-first}{left-…} \sepl \sepr *nothing*                 \sepr *nothing* \sepend
% Or:     {if-case}{else-case}{left-first}{left-…} \sepl B     right-…(may be *nothing*) \sepr \sepr     \sepend
\def\@expandable@if@equal@somechar#1#2#3#4\sepl#5#6\sepr#7\sepend{%
  \ifx#5\sepr% #5#6 = Empty string, a.k.a. "\sepr"
    % First case in the signature above (#5=\sepr, #6=*nothing*, #7=*nothing*)
    #2% But the left string was not empty, so we run else-case
  \else%
    % Second case in the signature above (#5=one char, #6=right…, #7=\sepr)
    \ifx#3#5% First character of left == first character of right
      \@expandable@if@equal{#1}{#2}#4\sepl\sepl#6\sepr\sepr\sepend% Recursion on the rest
    \else%
      #2% The strings are different at this character, execute #2.
    \fi%
  \fi%
}%
% Two possibilities:
%               #1       #2          #3    #4                       \sepl #5          \sepend
% \@expandable@if@equal {if-case}{else-case} \sepl *nothing*                \sepl <rest>      \sepend
% \@expandable@if@equal {if-case}{else-case} A     left-…(may be *nothing*) \sepl \sepl<rest> \sepend
\def\@expandable@if@equal#1#2#3#4\sepl#5\sepend{%
  \ifx#3\sepl% #3#4 = Empty string, a.k.a. "\sepl"
    % First case in the signature above (#3=\sepl, #4=*nothing*, #5=<rest>)
    \@expandable@if@equal@empty{#1}{#2}#5\sepend%
  \else%
    % First case in the signature above (#3=one char, #4=left…, #5=\sepl<rest>)
    \@expandable@if@equal@somechar{#1}{#2}{#3}{#4}#5\sepend%
  \fi%
}
\def\expandable@if@equal#1#2#3#4{%
  % \@expandable@if@equal#1\sepl#4%#2\sepl#4
  %
  % \@expandable@if@equal{#4}\sepl\sepl%
  \@expandable@if@equal{#3}{#4}#1\sepl\sepl#2\sepr\sepr\sepend%
}%

\makeatother
  • Unfortunately spaces are ignored ("a b" = "ab"). – Javier Bezos Jul 10 '16 at 9: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.