2

Inspired from Macro to access a specific member of a list.

I want to have a macro implementing the lisp functions (member item list) and (assoc id alist). The first checks if an item is contained in a list, the second accesses the value with the given id in an associative list.

The macro should be able to take a litteral list, or a macro.

Usage should be like:

\ifassoc{42}{1,2,3}{if-found}{if-not-found}
\ifassoc{42}{\somelist}{if-found}{if-not-found}
\ifassoc{b}{a/1,b/2,c/3}{\assocresult equals 2}{if-not-found}
\ifassoc{b}{\otherlist}{\assocresult can be used here}{if-not-found}
Suzanne Soy
  • 3,043

3 Answers3

3

This macro meets the spcifications:

\newcommand{\ifassoc}[4]{%
  \edef\dotheloop{%
    \noexpand\foreach \noexpand\a/\noexpand\b in {#2} {%
      \noexpand\IfEq{\noexpand\a}{#1}{%
        \noexpand\gdef\noexpand\memberresult{true}%
        \noexpand\xdef\noexpand\assocresult{\noexpand\b}%
        \noexpand\breakforeach%
      }{}%
    }%
  }%
  \xdef\assocresult{}%
  \xdef\memberresult{false}
  \dotheloop%
  \IfEq{\memberresult}{true}{#3}{#4}%
}%

MWE:

\documentclass{article}
\usepackage{tikz}
\usepackage{xstring}
\begin{document}

\newcommand{\ifassoc}[4]{%
  \edef\dotheloop{%
    \noexpand\foreach \noexpand\a/\noexpand\b in {#2} {%
      \noexpand\IfEq{\noexpand\a}{#1}{%
        \noexpand\gdef\noexpand\memberresult{true}%
        \noexpand\xdef\noexpand\assocresult{\noexpand\b}%
        \noexpand\breakforeach%
      }{}%
    }%
  }%
  \xdef\assocresult{}%
  \xdef\memberresult{false}
  \dotheloop%
  \IfEq{\memberresult}{true}{#3}{#4}%
}%

% Simple list of important numbers, given inline.
\ifassoc{2.72}{42,3.14}{
  This text will not be displayed.
}{
  Whoops, we forgot to put $e$ !
}

\ifassoc{2.72}{42,3.14,2.72}{
  $e$ is an important number. \texttt{\textbackslash assocresult=\assocresult}.
}{
  This text will not be displayed.
}

% Associative list of associative lists, given as a macro.
\def\collections{
  Colors/{1/red,2/green,3/blue},
  Foo/{1/foo,2/bar,3/baz,4/quux},
  Letters/{1/a,2/b,3/c,4/d,5/e}%
}
\ifassoc{Foo}{\collections}{
  % We can run \foreach on the result of \ifassoc.
  Found id \texttt{Foo}, associated with the pairs
  \foreach \i/\j in \assocresult {\i\ and \j, }%
  and that's all.
  % We can run \ifassoc again on the result of \ifassoc.
  \ifassoc{4}{\assocresult}{The element with id \texttt{4} is \assocresult.}{This text will not be displayed.}
  \ifassoc{5}{\assocresult}{This text will not be displayed.}{There is no element with id \texttt{5}.}
}{
  This text will not be displayed
}

\ifassoc{Digits}{\collections}{
  This text will not be displayed.
}{
  There is no collection with id \texttt{Digits}.
}

\end{document}

Output:

Output of the code above

Suzanne Soy
  • 3,043
  • (A) This solution works for \ifassoc{42}{|_|42|_|/ab|_|,|_|43/cd|_|}, but fails for, eg, \ifassoc{ab}{|_|ab|_|/42|_|,|_|cd/43|_|}, where |_| is space. The difference is in the way xstring does comparisons for numbers and plain strings. Spurious spaces may easily get into the list. Hence a robust solution should first trim the spurious spaces. Also, either slash (/) or comma (,) may uncommonly be active, making the list unparsable with \foreach. (B) If you do \foreach \a/\b in {42,3.14}{<do>}, \b will be 42 and 3.14 for the 2 items. – Ahmed Musa Sep 05 '12 at 17:25
2

As usual expl3 has easy tools for this. I hope the code is self-explanatory (well, there are comments...)

\documentclass{article}
\usepackage{xparse,expl3}

\ExplSyntaxOn
% variables:
\bool_new:N \l_georges_assoc_found_bool
\tl_new:N   \l_georges_assoc_item_tl
\tl_new:N   \l_georges_assoc_value_tl
\tl_new:N   \assocresult

% the main function:
\cs_new_protected:Npn \georges_ifassoc:nnTF #1#2#3#4
  {
    \bool_set_false:N \l_georges_assoc_found_bool
    \tl_clear:N \l_georges_assoc_value_tl
    % go through the comma list:
    \clist_map_inline:nn { #2 }
      {
        % separate item and (possible) value:
        \georges_separate_item_value:w ##1 // \q_stop
        % if we found the item stop the loop:
        \tl_if_eq:nVT { #1 } \l_georges_assoc_item_tl
          {
            \bool_set_true:N \l_georges_assoc_found_bool
            \clist_map_break:
          }
      }
    % make the value accessible:
    \tl_set_eq:NN \assocresult \l_georges_assoc_value_tl
    \bool_if:NTF \l_georges_assoc_found_bool { #3 } { #4 }
  }
\cs_generate_variant:Nn \tl_if_eq:nnT { nV }
\cs_generate_variant:Nn \georges_ifassoc:nnTF { no }

% separate item and value:    
\cs_new:Npn \georges_separate_item_value:w #1/#2/#3 \q_stop
  {
    % store item and remove trailing and leading space:
    \tl_set:Nx \l_georges_assoc_item_tl { \tl_trim_spaces:n { #1 } }
    % #3 = / if value given:
    \tl_if_eq:nnT { #3 } { / }
      { \tl_set:Nn \l_georges_assoc_value_tl { #2 } }
  }

% user command:
\NewDocumentCommand \ifassoc { mmmm }
  { \georges_ifassoc:noTF { #1 } { #2 } { #3 } { #4 } }
\ExplSyntaxOff

\begin{document}

\def\somelist{1,2,3,42}
\def\otherlist{a/1,b/2,c/3}

\ifassoc{42}{1,2,3}{if-found}{if-not-found}

\ifassoc{42}{\somelist}{if-found}{if-not-found}

\ifassoc{b}{a/1,b/2,c/3}{\assocresult\ equals 2}{if-not-found}

\ifassoc{b}{\otherlist}{\assocresult\ can be used here}{if-not-found}

\ifassoc{42}{ 42 /ab , 43/cd }{\assocresult}{F}

\ifassoc{ab}{ ab /42 , cd/43 }{\assocresult}{F}

\end{document}

enter image description here

cgnieder
  • 66,645
  • I would probably not absorb #3 and #4 in \georges_ifassoc:nnTF, as they will be picked up by the final \bool_if:NTF anyway. – Joseph Wright Sep 05 '12 at 20:33
  • Yes, that's true… When I started with \georges_ifassoc:nnTF I wasn't sure how it was going to look. They're leftovers :) – cgnieder Sep 05 '12 at 20:36
0

I hope I can state this fact here and now and give a full solution later. To illustrate my point (B) above, try:

\makeatletter
\newcount\cnta
\cnta\z@
\foreach \p/\q in {A,B}{%
  \global\advance\cnta\@ne
  \expandafter\xdef\csname result@\romannumeral\cnta\endcsname{\q}%
}
\edef\result{\result@i,\result@ii}
\show\result
\makeatother

A general scheme like the following will be preferred, but I guess there is already a package that offers this DB functionality.

\def\collections{
  1/colors:        1/red, 2/green, 3/blue;
  2/tech makers:   1/Toyota, 2/Apple, 3/Samsung, 4/Motorola, 5/Honda;
  3/IT firms:      1/Apple, 2/Microsoft, 3/Google, 4/Oracle;
  4/automakers:    1/Toyota, 2/Ford, 3/Mercedes, 4/GM, 5/Honda;
  5/units:         1/pt, 2/ex, 3/em, 4/cc, 5/in;
  6/sports:        1/basketball, 2/tennis, 3/football, 4/soccer;
  7/energy:        1/gas, 2/oil, 3/coal, 4/solar, 5/nuclear;
}

% Eg, \addrow{Continents: Africa, Asia, Europe, N.America, S.America, Ausie?}
\def\addrow#1{%

}
% Eg, \removerow{IT firms}
\def\removerow#1{%

}
% Eg, \addcol{units}{mm}
\def\addcol#1#2{%

}
% Eg, \removecol{sports}{tennis}
\def\removecol#1#2{%

}

% 1. What is the association (row) of automakers?
% 2. What is the association (column) of GM in automakers?

% Eg, \rowof{automakers}
\def\rowof#1{%

}
% Eg, \rowandcolof{automakers}{GM}
\def\rowandcolof#1#2{%

} 
Ahmed Musa
  • 11,742