Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I'm looking for a way to realize a macro with a switch/case control structure (or an array solution), as it exists in every programming language.

MWE:

\documentclass[ngerman,12pt]{article}
\usepackage{etoolbox}

\newcommand*\foo[1]{%
    \ifstrequal{#1}{givemea}{box}{}%
    \ifstrequal{#1}{drawmea}{circle}{}%
    \ifstrequal{#1}{applesidontlikeare}{green}{}%
    \ifstrequal{#1}{life}{is full of surprises}{}%
}
\begin{document}
\foo{life}
\end{document}

As you can think of, if I have let's say 500 entries the process will slow down very much, as it compares to every single value in the macro. A \break or \return command would be very useful, so the macro aborts if the value is found. Addition: My strings also will contain numbers (at every position).

Or are there any packages I can use for the job?

(Since there will be a lot of entries, I consider to split up the one macro into many: \fooa, \foob, ... So, if I invoke \foo{life} it checks the first letter l, and then invokes \fool{life}. That also will speed up the process.)

share|improve this question
    
Comparing every item is going to be slow: a split if possible is useful, even if it's only say halving the total length. –  Joseph Wright May 28 at 9:04
2  
Another alternative is an "associative jump array" using \csname foo@branch@#1\endcsname where the command \foo@branch@gievemea contains the code for this value. This will use the internal hash which is really efficient. You can guard this with \ifcsname and make it more robust with \detokenize. –  Stephan Lehmke May 28 at 9:09
    
@StephanLehmke I agree, but my strings will also have numbers, see my comment to David Carlisles answer –  musicman May 28 at 9:27
1  
@musicman No problem, just define the commands with \expandafter\newcommand\csname foo@branch@abc123(bar);baz\endcsname{expansion text} –  Stephan Lehmke May 28 at 9:29
1  
As for splitting up, using the internal hash will be efficient for millions of entries; see this question. –  Stephan Lehmke May 28 at 9:33
show 1 more comment

3 Answers 3

up vote 14 down vote accepted

You don't want a linear series of string equality tests you want a hash lookup:

\documentclass[12pt]{article}


\newcommand*\foo[1]{\csname FOO-#1\endcsname}
\newcommand*\setfoo[1]{\expandafter\def\csname FOO-#1\endcsname}

\setfoo{give1mea}{box}
\setfoo{draw2mea}{circle}
\setfoo{applesidontlikeare!!}{green}
\setfoo{life}{is full of surprises}
\setfoo{apple4dinner}{see?}


\begin{document}
\foo{life} \foo{apple4dinner}
\end{document}
share|improve this answer
    
Sorry, I can't use that, because I also will have number in the arguments: apple4dinner, 4bears, goodn8 –  musicman May 28 at 9:24
    
@musicman That isn't a problem just use \@namedef{apple4dinner}{this} (somewhere were @ is a letter eg after \makeatletter or in a package –  David Carlisle May 28 at 9:26
    
@musicman updated answer to demonstrate digits and ! in mames –  David Carlisle May 28 at 9:33
    
If \foo is not \long, shouldn't \setfoo also not be \long? –  Loop Space May 28 at 9:34
1  
@AndrewStacey I added a * :-) –  David Carlisle May 28 at 9:57
show 6 more comments

The expl3 programming layer for LaTeX3 provides \str_case:nnF and related functions: these only compare 'as far as required':

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_eq:NN \strcases \str_case:nnF
\ExplSyntaxOff
\newcommand*\foo[1]{%
  \strcases{#1}%
    {%
      {givemea}           {box}
      {drawmea}           {circle}
      {applesidontlikeare}{green}
      {life}              {is full of surprises}
    }%
    {No match found!}%
}
\begin{document}
\foo{life}
\end{document}

Note that for very long lists the comparison still has to be made for each item until a hit is found, which will get slower for very long lists when the item is near the end.

An alternative approach, similar to David's in the sense it uses a lookup table, is to use a prop:

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\prop_new:N \l_my_prop
\prop_put:Nnn \l_my_prop {givemea}           {box}
\prop_put:Nnn \l_my_prop {drawmea}           {circle}
\prop_put:Nnn \l_my_prop {applesidontlikeare}{green}
\prop_put:Nnn \l_my_prop {life}              {is~full~of~surprises}
\newcommand* \foo [1]
  {
    \prop_get:NnNTF \l_my_prop {#1} \l_tmpa_tl
      { \tl_use:N \l_tmpa_tl }
      { No~match~found! }
}
\ExplSyntaxOff
\begin{document}
\foo{life}
\end{document}

The data structure here (currently) uses only one name but that means that addition is relatively slow as the size rises. However, lookup should be not so severely affected (it uses a delimited macro), though again this drops off as the table size rises. (For 'big' data sets, TeX's hash table is probably the best way to create a lookup system, as in David's answer. See How to implement (low-level) arrays in TeX for more.)

share|improve this answer
    
Wouldn't a prop be more efficient here? –  Loop Space May 28 at 9:33
    
@AndrewStacey I've added a second approach: note that as we currently use on macro for the storage, things do slow down for big data sets. The hash table approach is probably better in those situations: mappings on the other hand are easier with a single macro. –  Joseph Wright May 28 at 10:23
    
What do you think: Which method – your 2 approaches and David Carlisles – comes with the best performance? –  musicman May 28 at 10:58
1  
@musicman As indicated in the link I've edited in, hash table based approaches have the performance advantage. However, they are not so easy to do certain tasks with (e.g. copy the whole table): the best choice depends on your real use case. –  Joseph Wright May 28 at 11:49
add comment

The following example compares the strings until a match if found. Then \@car takes the first argument and throws the remaining part until and including the next \@nil away, so that the following comparisons are skipped:

\documentclass[12pt]{article}
\usepackage{etoolbox}

\makeatletter
\newcommand*\foo[1]{%
  \def\foo@##1##2{%
    \ifstrequal{#1}{##1}{\@car{##2}}{}%
  }%
  \foo@{givemea}{box}%
  \foo@{drawmea}{circle}%
  \foo@{applesidontlikeare}{green}%
  \foo@{life}{is full of surprises}%
  \@nil
}
\makeatother
\begin{document}
\foo{givemea} \foo{life}
\end{document}

Result

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.