18

I frequently need to do something like this:

IF x < 1, DO a
ELSE IF x < 2, DO b
ELSE IF x < 3, DO c
ELSE, DO d

Using etoolbox, I end up nesting a lot of ifnumless, like this:

\ifnumless{x}{1}{a}{
    \ifnumless{x}{2}{b}{
        \ifnumless{x}{3}{c}{
            d
        }
    }
}

Is there a simpler way to achieve this?

Village
  • 13,603
  • 23
  • 116
  • 219
  • 1
    This thread might help: http://tex.stackexchange.com/questions/17676/conditional-cases-expression – Paulo Cereda Nov 26 '11 at 01:34
  • Well, TeX (and eTeX) has no \elseif, \elif; and needs \fi for every \if. So usually we always write bad-looking code. – Leo Liu Nov 26 '11 at 07:34

6 Answers6

15

A kind of "switchcase" can be easily programmes:

\documentclass{article}
\makeatletter
\def\ifnumcase#1{%
    \edef\elseif@{\string\elseif}\edef\endif@{\string\endif}%
    \def\number@test{#1pt}\ifnumcase@i}
\def\ifnumcase@i#1{%
    \def\ifnumcase@ii##1{%
        \csname
            @\ifdim\number@test#1pt first\else second\fi oftwo%
        \endcsname{##1\gobto@endif}\ifnumcase@i}%
    \edef\valeur@{\string#1}%
    \csname
        \ifx\valeur@\elseif@ idto@endif%
        \else\ifx\valeur@\endif@ relax\else ifnumcase@ii\fi
        \fi
    \endcsname}
\def\idto@endif#1\endif{#1}
\def\gobto@endif#1\endif{}
\makeatother
\begin{document}
\ifnumcase{7.5}% <- number to test
    {<1}{less than 1}
    {<3}{less than 3}
    {>5}{greater than 5}
    % add other tests if needed
\elseif
    between 3 and 5% all tests faild
\endif

\ifnumcase{3.14}% <- number to test
    {<2}{less than 1}
    {<3}{less than 3}
    {<4}{less than 4}
    {<10}{less than 10}
    % add possible other tests
\endif
\end{document}

EDIT: an even more complete and expandable solution with =, <, >, <= or <= tests:

\documentclass{article}
\usepackage[T1]{fontenc}
\makeatletter
\def\elseif{\elseif}\def\endif{\endif}
\def\ifnumcase#1#2{%
    \expandafter\ifx\expandafter\elseif\@car#2\@nil\expandafter\@firstoftwo
    \else
        \expandafter\ifx\expandafter\endif\@car#2\@nil\expandafter\expandafter\expandafter\@gobbletwo
        \else\expandafter\expandafter\expandafter\@secondoftwo
        \fi
    \fi
        \idto@endif
        {\if@eqin#2=\@nil{\if@dimwitheq{#1}#2\@nil}{\ifdim#1pt#2pt }
            \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi\exec@arg{\ifnumcase@i{#1}}%
        }%
    }
\def\ifnumcase@i#1#2{\ifnumcase{#1}}
\def\if@eqin#1=#2\@nil{%
    \ifx\@empty#2\@empty\expandafter\@secondoftwo
    \else
        \ifx\@empty#1\@empty\expandafter\expandafter\expandafter\@secondoftwo
        \else\expandafter\expandafter\expandafter\@firstoftwo
        \fi
    \fi}
\def\if@dimwitheq#1#2=#3\@nil{\unless\ifdim#1pt\if<#2>\else<\fi#3pt }
\def\exec@arg#1#2\endif{#1}
\def\idto@endif#1\endif{#1}
\def\gobto@endif#1\endif{}
\makeatother
\begin{document}
\ifnumcase{3}
    {<=1}{lt or equal 1}
    {>=3}{gt or equal 3}
\elseif
    beteween 1 and 3
\endif

\ifnumcase{4}% <- number to test
    {<=1}{less or equal 1}
    {<=3}{less or equal 3}
    {=4}{equal 4}
    {>=5}{greater or equal 5}
    % add other tests if needed
\elseif
    between 3 and 5% all tests faild
\endif

\edef\foo{\ifnumcase{5}% <- number to test
    {<=1}{less or equal 1}
    {<=3}{less or equal 3}
    {=4}{equal 4}
    {>=5}{greater or equal 5}
    % add other tests if needed
\elseif
    between 3 and 5% all tests faild
\endif}\meaning\foo

\ifnumcase{3.14}% <- number to test
    {<2}{less than 1}
    {<3}{less than 3}
    {<4}{less than 4}
    {<10}{less than 10}
    % add possible other tests
\endif
\end{document}
unbonpetit
  • 6,190
  • 1
  • 20
  • 26
  • What of \def\x{} \edef\x{% \ifsomecase\ifx\x \y{equal to \string\y} \z{equal to \string\z} % add possible other tests \elseif equal to none% \endif } \edef\x{% \ifsomecase\ifnum{2.1} {<=2}{equal or less than 2} {>=4}{equal or greater than 4} % add possible other tests \elseif equal to none% \endif } – Ahmed Musa Nov 26 '11 at 21:39
  • "village" wanted to compare numbers... – unbonpetit Nov 26 '11 at 22:13
  • But you've already exceeded what he wanted. We're now dealing with the more interesting exercises. :) Actually, adding \ifx, \ifstrcmp, etc. is routine. \def\elseif{\elseif} is like LaTeX3 quark. Use with care. – Ahmed Musa Nov 26 '11 at 22:23
  • I don't think I have exceeded what he wants. After all, I have implemented tests on numbers. For sure, expandibility is probably a funny feature he didn't ask. I do not know what is a latex3 quark. Where in my code do you think I am careless? – unbonpetit Nov 26 '11 at 22:48
11

Depending on who else needs to use the code, would lualatex work?

\documentclass{article}
\begin{document}
\directlua{
x=1.5
}
\directlua{
if x<1 then
  tex.print('a')
elseif x<2 then
  tex.print('b')
elseif x<3 then
  tex.print('c')
else
  tex.print('d')
end
}
\end{document}
Mike Renfro
  • 20,550
10

If you have it only to compare with integers then it is easy:

\documentclass{minimal}   
\def\getInteger#1{\expandafter\stripDecimals#1..!!}
\def\stripDecimals#1.#2.#3!!{\ifx\relax#1\relax0\else#1\fi}

\def\x{\getInteger{0.5}}      
\begin{document}   

\ifcase\x   % 0
 lt 1\or    % 1
 lt 2\or    % 2
 lt 3       % 3
\else gt 3  % else
\fi

\end{document}
8

Best Practices ? Question not specific enough, this depends of the engine, then you want to use TeX, LaTeX or LuaTeX etc. You want a concise code or fast code?

With TeX something like the code below is possible (there is other possibilities for example without \ifdim

\documentclass[11pt]{scrartcl}   
\begin{document}   
\def\x{1.5}   

\ifdim \x pt< 1pt a
   \else 
     \ifdim \x pt < 2 pt b
       \else  
          \ifdim \x pt < 3pt c
            \else  d  
           \fi
     \fi
\fi
\end{document}  
Marco Daniel
  • 95,681
Alain Matthes
  • 95,075
5

An expandable and more complete 'switch' solution is:

\documentclass{article}
\makeatletter
\def\identoendif#1\endif{#1}
\def\oneoftoendif#1#2\endif{#1}
\def\gobbletoendif#1\endif{}
\newcommand*\ifstrsame[2]{%
  \@nameuse{@\ifnum\pdfstrcmp{\detokenize{#1}}%
    {\detokenize{#2}}=0first\else second\fi oftwo}%
}
\def\dimexpr@i#1{#1\dimexpr}
\def\ifnumcase#1{%
  \ifstrsame{#1}\elseif\gobbletoendif{%
    \ifstrsame{#1}\endif\@gobble\ifnumcase@i
  }{#1}%
}
\def\ifnumcase@i#1#2{%
  \ifstrsame{#2}\elseif\identoendif{%
    \ifstrsame{#2}\endif{}{%
      \ifdim\dimexpr#1pt\relax\dimexpr@i#2pt\relax
        \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
          {\oneoftoendif}{\ifnumcase@ii{#1}}%
    }%
  }%
}
\def\ifnumcase@ii#1#2#3{\ifnumcase@i{#1}{#3}}
\makeatother

%% Examples:
\edef\x{%
  \ifnumcase{6}% <- number to test
    {<1}{less than 1}
    {<3}{less than 3}
    {>5}{greater than 5}
    % add other tests if needed
  \elseif
    between 3 and 5% all tests failed
  \endif
}
\show\x -> greater than 5

\edef\x{%
  \ifnumcase{3.14}% <- number to test
    {<2}{less than 1}
    {<3}{less than 3}
    {<4}{less than 4}
    {<10}{less than 10}
    % add possible other tests
  \endif
}
\show\x -> less than 4

\edef\x{%
  \ifnumcase{314}% <- number to test
    {<2}{less than 1}
    {<3}{less than 3}
    {<4}{less than 4}
    {<10}{less than 10}
    % add possible other tests
  \endif
}
\show\x -> empty

\begin{document}
% Nothing to do:
\ifnumcase\elseif\endif
\ifnumcase{3}\elseif\endif
\ifnumcase\endif
\ifnumcase{3}\endif
\end{document}

unbonpetit's latest solution fails for

\ifnumcasse{3}
    {=<1}{lt or equal 1}
    {=>3}{gt or equal 3}
\elseif
    beteween 1 and 3
\endif

and

\def\gobto@endif#1\endif{}

is redundant.

A more general solution is

\makeatletter
\def\elseif{\@gobble\elseif}
\def\endif{\@gobble\endif}
\def\swap#1#2{#2#1}
\def\ifstrsame#1#2{%
  \ifnum\pdfstrcmp{\detokenize{#1}}{\detokenize{#2}}=\z@
  \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\def\ifstrnull#1{%
  \ifnum\pdfstrcmp{\detokenize{#1}}{}=\z@
  \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\def\ifcmdeq#1#2{%
  \ifx#1#2\endif\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\def\if@eqin#1=#2\@nil{%
  \ifstrnull{#2}\@secondoftwo{\ifstrnull{#1}\@secondoftwo\@firstoftwo}%
}
\def\if@dimwitheq#1#2=#3\@nil{\unless\ifdim#1pt\if<#2>\else<\fi#3pt }
\def\docasecallback#1#2\endif{#1}
\def\doelsepart#1\endif{#1}
\def\ifnumcase#1#2{%
  \expandafter\ifcmdeq\@car#2\@nil\elseif\@firstoftwo{%
    \expandafter\ifcmdeq\@car#2\@nil\endif\@gobbletwo\@secondoftwo
  }%
  \doelsepart{%
    \expandafter\expandafter\expandafter
    \if@eqin\checkcomparators#2\@nil=\@nil{%
      \expandafter\expandafter\expandafter\swap
      \expandafter\expandafter\expandafter
      {\checkcomparators#2\@nil}{\if@dimwitheq{#1}}\@nil
    }{%
      \ifdim#1pt#2pt %
    }
    \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    \docasecallback{\ifnumcase@i{#1}}%
  }%
}
\def\ifnumcase@i#1#2{\ifnumcase{#1}}
\def\checkcomparators#1#2#3\@nil{%
  \romannumeral
  \ifstrsame{#1}={\ifstrsame{#2}<{0<=#3}{\ifstrsame{#2}>{0>=#3}{0 #1#2#3}}%
  }{0 #1#2#3}%
}
\makeatother
Ahmed Musa
  • 11,742
  • I do not find your little game funny. Ok, you win, you proove that you are much better than me and your solution are much more "general". Bye. – unbonpetit Nov 27 '11 at 12:11
  • @unbonpetit Oh, I thought it was just another online game. Nothing to prove and nothing has been proved! Sorry. Bye, bye. – Ahmed Musa Nov 27 '11 at 17:03
  • 1
    An online game??? Is it a game for you to give "better" or "more general" solutions than me? To say that my code "fails", that it contains "redundant" parts? For sure, it is a very easy to find a case in which a code does not work. It is probably funny to suggest that I am not careful enough with "quarks". Let me say that I answered the question because I just wanted to help "Village". Nothing esle. I was not looking for someone to correct me. And I was not looking for someone to compete with. You misunderstood my motivation! – unbonpetit Nov 27 '11 at 18:29
1

Here's a solution to the problem of some elseif equivalent that is undoubtedly inefficient and probably encompasses numerous bad practices (including using the archaic ifthen package and using \else as a delimiter), but is very short and seems to work.

\RequirePackage{ifthen}
\def\fifi\relax
\newcommand{\ifif}{}             % I have no idea why this line is needed.
\long\def\ifif#1#2\else#3\fifi{\ifthenelse{#1}{#2}{#3\fifi}}

A usage example (which makes a lot more sense when you use SageTeX):

\documentclass{article}
\usepackage{mathtools}
\DeclarePairedDelimiter\floor{\lfloor}{\rfloor}

\RequirePackage{ifthen}
\def\fifi\relax
\newcommand{\ifif}{}
\long\def\ifif#1#2\else#3\fifi{\ifthenelse{#1}{#2}{#3\fifi}}

\let \oldmbox=\mbox
\renewcommand{\mbox}[1]{%
    \ifif{\equal{floorHalfS}{#1}}%
        \floor*{\tfrac{1}{2}s}%
    \else\ifif{\equal{alphaS}{#1}}%
        \alpha(s)%
    \else\ifif{\equal{lprime}{#1}}%
        \ell'%
    \else\ifif{\equal{tprime}{#1}}%
        t'%
    \else\ifif{\equal{qprime}{#1}}%
        q'%
    \else\ifif{\equal{ell}{#1}}%
        \ell%
    \else\ifif{\equal{kprime}{#1}}%
        k'%
    \else%
        \oldmbox{#1}%
    \fifi%
}


\begin{document}

\[\frac{\sqrt{\mbox{floorHalfS}^2 + 2\mbox{ell}}}{3\mbox{alphaS}}\]

\end{document}

The result:

enter image description here