# How retrieve items in a list of words by number?

I'm working on a character sheet for the Fate RPG, which rates its skills with a numerical scale that corresponds to an adjective scale. I've been trying to write a macro that will take \skillAdj{4} and turn it into "Great" in a more elegant way than with these conditionals.

Ideally, I'd be able to do lookup in both directions, but I'm more concerned with going from number to adjective.

\usepackage{calc}
\usepackage{ifthen}
%
\newcounter{skillcounter}
\set{skillcounter}{0}
%
\newcommand[1]{\skillAdj}{\ifthenelse{\equal{#1}{8}
Legendary
}
{\ifthenelse{\equal{#1}{7}
Epic
}
{\ifthenelse{\equal{#1}{6}
Fantastic
}
{\ifthenelse{\equal{#1}{5}
Superb
}
{\ifthenelse{\equal{#1}{4}
Great
}
{\ifthenelse{\equal{#1}{3}
Legendary
}
{\ifthenelse{\equal{#1}{2}
Legendary
}
{\ifthenelse{\equal{#1}{1}
Legendary
}
{\ifthenelse{\equal{#1}{0}
Mediocre
}
{\ifthenelse{\equal{#1}{-1}
Poor
}
{\ifthenelse{\equal{#1}{-2}
Terrible
}
{\ifthenelse{\equal{#1}{-3}
Awful
}
{\ifthenelse{\equal{#1}{-4}
Abysmal
}
{Error}}}}}}}}}}}}}


This is the scale.

+8 Legendary
+7 Epic
+6 Fantastic
+5 Superb
+4 Great
+3 Good
+2 Fair
+1 Average
+0 Mediocre
-1 Poor
-2 Terrible
-3 Awful
-4 Abysmal

I'd really appreciate any helpful advice people have. I haven't found anything in the documentation of any of the packages that seem like they might provide this function.

## 5 Answers

There are several ways to accomplish your need. Here's one with expl3:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\skillAdj}{m}
{
\int_case:nnn { #1 }
{
{8}{Legendary}
{7}{Epic}
{6}{Fantastic}
{5}{Superb}
{4}{Great}
{3}{Good}
{2}{Fair}
{1}{Average}
{0}{Mediocre}
{-1}{Poor}
{-2}{Terrible}
{-3}{Awful}
{-4}{Abysmal}
}
{Error}
}
\ExplSyntaxOff

\begin{document}

X is \skillAdj{8}

Y is \skillAdj{5}

Z is \skillAdj{-4}

\texttt{egreg} is \skillAdj{10}

Come on! How can it be?

\end{document}


You can also say \skillAdj{\value{skillcounter}}, if you want and have defined the counter.

Notice that in your definition you have some glitches:

\set{skillcounter}{0}


should be

\setcounter{skillcounter}{0}


while

\newcommand[1]{\skillAdj}{\ifthenelse{\equal{#1}{8}...


should be

\newcommand{\skillAdj}[1]{\ifthenelse{\equal{#1}{8}...


A very similar solution using xstring:

\documentclass{article}

\usepackage{xstring}
\newcommand{\skillAdj}[1]{%
\IfEqCase{#1}{%
{8}{Legendary}%
{7}{Epic}%
{6}{Fantastic}%
{5}{Superb}%
{4}{Great}%
{3}{Legendary}%
{2}{Legendary}%
{1}{Legendary}%
{0}{Mediocre}%
{-1}{Poor}%
{-2}{Terrible}%
{-3}{Awful}%
{-4}{Abysmal}%
}[Error]
}

\newcounter{skillcounter}

\begin{document}

\setcounter{skillcounter}{8}

X is \skillAdj{\theskillcounter}

Y is \skillAdj{5}

Z is \skillAdj{-4}

\texttt{egreg} is \skillAdj{10}

Come on! How can it be?

\end{document}


Notice that here you have to use \theskillcounter as \value{skillcounter} wouldn't work.

• Thanks @egreg. Your solution looks like it will be easier to manage. – JakeO May 13 '13 at 17:04
• @JakeO I added a version using xstring; but I'd prefer the first one that, however, requires a very up-to-date TeX distribution. – egreg May 13 '13 at 17:10

One more TeX \ifcase version, without extra ifnum:

\def\skillAdj#1{%
\count255=8\advance\count255by-#1 %
\ifcase\the\count255 %
Legendary%
\or Epic%
\or Fantastic%
\or Superb%
\or Great%
\or Good%
\or Fair%
\or Average%
\or Mediocre%
\or Poor%
\or Terrible%
\or Awful%
\or Abysmal%
\else Error%
\fi%
}

\def\test#1{#1 \skillAdj{#1}}
\obeylines\tt
\test{+8}
\test{+7}
\test{+6}
\test{+5}
\test{+4}
\test{+3}
\test{+2}
\test{+1}
\test{+0}
\test{-1}
\test{-2}
\test{-3}
\test{-4}
\test{-7}
\test{+9}

\newcount\cnt\cnt5

\test{\the\cnt}

\bye


You could also use TeX's in-built \ifcase construct as follows- no packages necessary!

% arara: pdflatex
\documentclass{article}

\newcommand{\skillAdj}[1]{%
\ifcase\numexpr#1+4\relax
Abysmal      % -4
\or Awful    % -3
\or Terrible % -2
\or Poor     % -1
\or Mediocre  % 0
\or Average   % 1
\or Fair      % 2
\or Good      % 3
\or Great     % 4
\or Superb    % 5
\or Fantastic % 6
\or Epic      % 7
\or Legendary % 8
\else
Error
\fi
}

\newcounter{skillcounter}

\begin{document}

\setcounter{skillcounter}{-5}

\loop
\theskillcounter: \skillAdj{\theskillcounter}
\stepcounter{skillcounter}\par
\ifnum\value{skillcounter}<10 \repeat

\end{document}


If your smallest value (-4 in the above) changes, just update the line

\ifcase\numexpr#1+4\relax


accordingly.

• Saver is \ifcase\numexpr(#1)+4\relax. The final \relax is removed automatically by \numexpr and prevents that \numexpr looks for a continuation of the expression in the next tokens. – Heiko Oberdiek May 14 '13 at 15:55

Other answers have exploited the numeric character of one of the member of the correspondance. Here is a method which establishes a bidirectional correspondance. It does use the numeric character of the levels to allow some flexibility in the input, like using a count register, or having arithmetic operations on them (uses the e-TeX \numexpr)

The code uses LaTeX and some of its internal macros (with @'s), as I guess the majority of people here do not use Plain TeX.

\documentclass{article}

\makeatletter
% because we will be using \@namedef, \@nameuse, \@ifundefined from the LaTeX
% kernel.

% but our own private macros are defined *without* @ signs:
%    1. having @ signs everywhere gives headaches
%    2. *not* using @ signs is the safer road to avoid conflicts with packages,
%    as they traditionally do use @ signs in their private macro names.

\def\gobtilstop #1\stop {}
\def\gobtilz    #1\z {}

\def\SetUpTwoWays #1{\gobtilstop #1\gobtilz\stop\setuptwoways #1\z}

\def\setuptwoways #1,#2\z
{%
\@namedef {SkillToAdj\the\numexpr#1}{#2}%
\@namedef {AdjToSkill#2}{#1}%
\SetUpTwoWays
}

% we used \the\numexpr to allow the argument of \SkillToAdj to be a count
% register, or something like 2+3 for example.

\def\SkillToAdj #1%
{%
\@ifundefined{SkillToAdj\the\numexpr#1}
{error}
{\@nameuse{SkillToAdj\the\numexpr#1}}%
}

\def\AdjToSkill #1%
{%
\@ifundefined{AdjToSkill#1}
{1000000000} % needs to be a number if the output should be
% used as input for \SkillToAdj
{\@nameuse{AdjToSkill#1}}%
}
\makeatother

% Removing the \the\numexpr above would allow bidirectional correspondances
% between any kind of data.

% Here, the first entry is numeric, but the list given here could be in an
% arbitrary order, doesn't have to be decreasing or increasing, and there could
% be gaps too.

\SetUpTwoWays
{+8,Legendary}{+7,Epic}{+6,Fantastic}{+5,Superb}
{+4,Great}{+3,Good}{+2,Fair}{+1,Average}{+0,Mediocre}
{-1,Poor}{-2,Terrible}{-3,Awful}{-4,Abysmal}\stop

\def\AdjSucc #1{\SkillToAdj{\AdjToSkill{#1}+1}}

\begin{document}

\tt
\newcount\cnta
\cnta -5
\loop
\the\cnta{}
$\longrightarrow$ \SkillToAdj{\cnta}
$\longrightarrow$ \AdjToSkill{\SkillToAdj{\cnta}}
$\longrightarrow$ \SkillToAdj{\AdjToSkill{\SkillToAdj{\cnta}}}\endgraf
\ifnum\cnta<10 \advance\cnta 1 \repeat

Terrible (badly input): \AdjToSkill{ Terrible}

Level $3*2$ is \SkillToAdj{3*2}

Level $5-7$ is \SkillToAdj{5-7}

Terrible is worse than \AdjSucc{Terrible} which is worse than \AdjSucc{\AdjSucc{Terrible}}

\end{document}


• Thank you very much for this, the bidirectional hookup is useful. So far all of the answers people have given have been beyond my understanding--I've only used LaTeX in the past to format papers and such. When I have more time I'll try to truly understand how the code works, but until then these solutions are all very helpful. – JakeO May 14 '13 at 17:28
• Glad it helps! the idea is to automatize the creation of macros such as \SkillToAdj2 which expands to Fair. But to get 2 inside the macro name one uses the \csname primitive, here hidden in LaTeX \@namedef. Conversely \AdjToSkillFair is defined to expand to +2. It could have been done simply by \def\AdjToSkillFair {+2}. But I used also \@namedef as there were various other strings to use, besides Fair. As the +2 may be input in various ways, like 2 or 1+1, it was filtered through \numexpr. Text strings should also be filtered for more robust code allowing spaces. – user4686 May 14 '13 at 17:57

The following is a mild modification to PGF/TikZ: How to store strings in array? The adjustment accommodates for negative values (excluding a count of the elements since it may not be required):

\documentclass{article}
\usepackage{etoolbox}% http://ctan.org/pkg/etoolbox
\newcounter{listtotal}\newcounter{listcntr}%
\newcommand{\skilladj}[1]{% \skilladj{<number>}
\setcounter{listcntr}{-5}% Start from -4
\renewcommand*{\do}[1]{\stepcounter{listcntr}\ifnum\value{listcntr}=#1\relax##1\fi}%
\expandafter\docsvlist\expandafter{\skilladjarray}% Process list again
}
% Skill Adjustment array
\newcommand{\skilladjarray}{Abysmal,Awful,Terrible,Poor,Mediocre,Average,Fair,Good,Great,Superb,Fantastic,Epic,Legendary}%
\begin{document}
\verb|\skilladj{-2}:|\ \skilladj{-2} \par
\verb|\skilladj{2}:|\ \skilladj{2} \par
\verb|\skilladj{0}:|\ \skilladj{0} \par
\verb|\skilladj{5}:|\ \skilladj{5} \par
\verb|\skilladj{-5}:|\ \skilladj{-5} \par
\verb|\skilladj{9}:|\ \skilladj{9} \par
\verb|\skilladj{8}:|\ \skilladj{8}
\end{document}

• Thank you very much @Werner, this is exactly what I need! – JakeO May 13 '13 at 16:56