26

I'd like to have command built around a switch-case environment in LaTeX, much like the example below:

\documentclass{article}

\usepackage{xstring}

\newcommand{\dothis}[1]{%
\IfStrEqCase{#1}{{a}{so you typed a}
    {b}{now this is b}
    {c}{you want me to do c?}}
    [nada]
}

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{e}

\end{document}

My problem with it is that it requires the xstring package. Is there any other way to do this? Preferably without loading additional packages and avoiding disgraceful thickets of \if-\else-\fi statements?

  • Are we allowed to use \pdfstrcmp? – Joseph Wright Jul 20 '12 at 20:27
  • @JosephWright: Any ideas are welcome. :) – Count Zero Jul 20 '12 at 20:28
  • After various troubles with alternatives, I ended up using the \str_case:nnF construct that expl3 makes available through e.g. \usepackage{xparse}. I was initially put off by the unusual characters in the macro name, but that was a mistake — it just has to be surrounded by \ExplSyntaxOn, \ExplSyntaxOff to make that syntax valid. – Evgeni Sergeev Feb 17 '16 at 10:19
  • Just a note that \str_case:nnF is documented in interface3.pdf. – Evgeni Sergeev Feb 17 '16 at 10:33
22

The question asks to avoid packages, so while this is the method used by expl3 in \str_case:nnF I have recoded it with minimal support. The only package I've used is pdftexcmds, which is needed as the \pdfstrcmp primitive from pdfTeX is called \strcmp by XeTeX and has to be implemented in Lua for LuaTeX. This is easy enough to do without the package, but obscures the method: ask a separate question if required!

The general idea here is to set up a comparison loop in which the test is done expandably by \pdfstrcmp. The test string is passed every time, with the 'true' string inserted if there is a match by the 'tidy up' code. If there is no match at all then the 'else' code is inserted. The \romannumeral business means that it always requires exactly two expansions to do the work here:

\documentclass{article}
\usepackage{pdftexcmds}
\makeatletter
\newcommand*{\dothis}[1]{%
  \stringcases
    {#1}%
    {%
      {a}{so you typed a}%
      {b}{now this is b}%
      {c}{you want me to do c?}%
    }%
    {[nada]}%
}
\newcommand{\stringcases}[3]{%
  \romannumeral
    \str@case{#1}#2{#1}{#3}\q@stop
}
\newcommand{\str@case}[3]{%
  \ifnum\pdf@strcmp{\unexpanded{#1}}{\unexpanded{#2}}=\z@
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
    {\str@case@end{#3}}
    {\str@case{#1}}%
}
\newcommand{\str@case@end}{}
\long\def\str@case@end#1#2\q@stop{\z@#1}
\makeatother

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{e}

\end{document}
  • Not sure which other methods you are referring to. I don't see how the loop could be avoided? Or is the \pdfstrcmp primitive particularly slow? – Bruno Le Floch Jul 20 '12 at 23:33
  • @BrunoLeFloch I was thinking in particular of a non-expandable version where you you store in detokenized in a macro and use a series of baked-in \ifx tests. \ifx is fast, and \pdfstrcmp is slower, but you then have the nested \if ... \fi business we were asked to avoid. – Joseph Wright Jul 21 '12 at 6:20
  • I benchmarked, and \ifx seemed just as slow as \pdfstrcmp, as long as the tokens' string representations are not too long. \ifx is only very fast for comparisons between macros let to each other (since TeX then only has to compare one number). – Bruno Le Floch Jul 28 '12 at 8:35
  • @BrunoLeFloch OK, I'll edit that out then :-) – Joseph Wright Jul 28 '12 at 8:47
13

Though it's most likely too late for the OP, I just worked out my own switch and thought I'd share it here for future readers. My solution uses solely the package xifthen (ifthen suffices too, but I already had xifthen installed...).

% Switch implementation
\usepackage{xifthen}
\newcommand{\ifequals}[3]{\ifthenelse{\equal{#1}{#2}}{#3}{}}
\newcommand{\case}[2]{#1 #2} % Dummy, so \renewcommand has something to overwrite...
\newenvironment{switch}[1]{\renewcommand{\case}{\ifequals{#1}}}{}

% Example: Pick color by ID
\newcommand{\incolor}[2]{
    \begin{switch}{#1}
        \case{1}{\color{red}}
        \case{2}{\color{blue}}
        \case{3}{\color{green}}
        \case{4}{\color{black}}
        #2
    \end{switch}
}

This code compiles perfectly fine in my TeXMaker (ofc. you'll need the color-package for this example, but it's not part of the switch). The example colors a given input in a color defined by an ID I chose (Usage: \incolor{ID}{Content}). I used it for shorthand notations of lots of things (e.g. \lp1, \lp2, ... for parentheses in different colors using \newcommand{\lp}[1]{\incolor{#1}{\langle}}). Feel free to experiment ;)

I could imagine this to be expanded to a solution using only built-in control structures, but I've been too lazy for now to do so, yet \ifx and \else should do the trick.

  • 1
    Unfortunately there is no easy way to provide a default base case if nothing matches, with this approach. – Evgeni Sergeev Feb 8 '16 at 9:49
7

The following is from catoptions package. The following \ifcasse is \cptifcasse in catoptions package. I think Count Zero should simply load an existing package for his task.

\documentclass{article}
\usepackage{pdftexcmds}
\makeatletter
\long\def\am@alltoendif#1\endif{\unexpanded{#1}}
\long\def\am@domatchcode#1#2\endif{\unexpanded{#1}}
\long\def\amifcond#1\fi{\csname @#1first\else second\fi oftwo\endcsname}
\long\def\amifstrcmp#1#2{%
  \amifcond\if0\pdf@strcmp{\detokenize{#1}}{\detokenize{#2}}\fi
}
\def\ifcasse#1#2{%
  \amifstrcmp{#1}\ifnone{%
    \am@alltoendif
  }{%
    \amifstrcmp{#1}\endif{}{%
      \amifstrcmp{#2}\ifnone{%
        \am@alltoendif
      }{%
        \amifstrcmp{#2}\endif{}{\am@ifcasse{#1}{#2}}%
      }%
    }%
  }%
}
\def\am@ifcasse#1#2#3{%
  \amifstrcmp{#3}\ifnone{%
    \am@alltoendif
  }{%
    \amifstrcmp{#3}\endif{}{%
      #1{#2}{#3}\am@domatchcode{\am@ifcasse@i{#1}{#2}}%
    }%
  }%
}
\def\am@ifcasse@i#1#2#3{\am@ifcasse{#1}{#2}}

% The user can also define his own logical test command and pass it as the first 
% argument of \ifcasse. For example, we define \ifnumtest:

\def\ifnumtest#1#2{\amifcond\ifnum#1#2\fi}

% We rewrite Count Zero's macro:
\newcommand*{\dothis}[1]{%
  \par
  \ifcasse\amifstrcmp{#1}%
    {a}{so you typed a}
    {b}{now this is b}
    {c}{you want me to do c?}
  \ifnone
    [no match]%
  \endif
}
\makeatother

\begin{document}
\dothis{a}
\dothis{b}
\dothis{c}
\dothis{e}

% Weird test that correctly gives \x as 'empty':
\edef\x{%
  \ifcasse\amifstrcmp{x}
  \endif
}
%\show\x

% Weird test that gives \x as 'no match':
\edef\x{%
  \ifcasse\amifstrcmp{x}
  \ifnone
    [no match]%
  \endif
}
\par``\x''.

% Number test:
\edef\x{%
  \ifcasse\ifnumtest{2}
    {=1}{equal to 2}
    {<3}{less than 3}
    {>4}{greater than 4}
  \ifnone
    no match%
  \endif
}
\par``\x''.
\end{document}
  • Defining a string comparison macro which works like \pdfstrcmp is probably not reasonable (there was some code for an expandable comparison in expl3, but it dropped spaces so does not count). I was only meaning that it's not too hard to test the engine version and pick the correct name for the functionality (or to define in Lua). – Joseph Wright Jul 21 '12 at 6:22
  • @JosephWright: You're right. I have seen one edge failure mode with the \strcmp command that I tried to create, but I don't have the time to fix it now. I have therefore edited my answer. – Ahmed Musa Jul 21 '12 at 14:47
3

Here is a solution via macro definitions (a single test makes the choice):

\documentclass{article}
\makeatletter
\newcommand\addcase[3]{\expandafter\def\csname\string#1@case@#2\endcsname{#3}}
\newcommand\makeswitch[2][]{%
  \newcommand#2[1]{%
    \ifcsname\string#2@case@##1\endcsname\csname\string#2@case@##1\endcsname\else#1\fi%
  }%
}
\makeatother

\makeswitch[nada]\dothis
\addcase\dothis{a}{so you typed a}
\addcase\dothis{b}{so you typed b}
\addcase\dothis{c}{you want me to do c?}

\begin{document}
\dothis{a}

\dothis{b}

\dothis{c}

\dothis{e}

\end{document}
  • 1
    I was just looking to see if this type of approach was represented. I find it the most excellent of approaches. (+1) – Steven B. Segletes Jan 18 '17 at 17:06
3

Based on the great answer by Thev and updated by MaxD I have implemented a \default command for switch. I used ifthen package:

\newboolean{default}
\newcommand{\case}{}
\newcommand{\default}{}

\newenvironment{switch}[1]{%
    \setboolean{default}{true}
    \renewcommand{\case}[2]{\ifthenelse{\equal{#1}{##1}}{%
        \setboolean{default}{false}##2}{}}%
    \renewcommand{\default}[1]{\ifthenelse{\boolean{default}}{##1}{}}
}{}

Then implement it as such:

  \begin{switch}{\program}  
    \case{2}{\twodayprogram}  
    \case{4}{\fourdayprogram}  
    \default{\first\ \last\ missing program\pagebreak}  
  \end{switch}

in case anyone is curious, this was a program for an off-season sports conditioning program and was being looped with datatool, the commands \twodayprogram and \fourdayprogram implement the program for a particular athlete

1

This is a simple extension to Thev's answer, adding support for a default case.

% Switch implementation
\usepackage{xifthen}
\newcommand{\ifequals}[4]{\ifthenelse{\equal{#1}{#2}}{#3}{#4}}
\newcommand{\case}[2]{#1 #2} % Dummy, so \renewcommand has something to overwrite...
\newenvironment{switch}[1]{\renewcommand{\case}{\ifequals{#1}}}{}

% Example: Pick color by ID
\newcommand{\incolor}[2]{
    \begin{switch}{#1}
        \case{1}{\color{red}}{
        \case{2}{\color{blue}}{
        \case{3}{\color{green}}{
        \case{4}{\color{black}}{
        \color{cyan}
        }}}}
    \end{switch}
    #2
}
0

In LuaTeX you can use Lua tables to emulate a switch statement.

% arara: lualatex
\documentclass{article}

\newcommand*\dothis[1]{%
  \directlua{
    local cases = {
        a = "so you typed a",
        b = "now this is b",
        c = "you want to do c?"
    }

    if cases["\luaescapestring{#1}"] \string~= nil then
        tex.sprint(cases["\luaescapestring{#1}"])
    else
        tex.sprint("[nada]")
    end
  }
}

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{d}

\end{document}

enter image description here

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.