6

Does TikZ offer a reasonably native/concise way of expressing 'if the loop variable is an element of this set, then...'?

Remarks. An example for what I am asking for is the pseudo-code

 \pgfmathparse{\index in {0,2,4,6,8}} 

which of course should evaluate to the boolean 1 if and only if \index==0 OR \index==2 OR \index==4 OR \index==6 OR \index==8 within the otherwise non-pseudo code

\documentclass{article}

\usepackage{tikz}
\usepackage{ifthen}

\begin{document}

\begin{tikzpicture}
    \foreach \index in {0,1,...,101}{
        \ifthenelse{
            \pgfmathparse{\index in {0,2,4,6,8}} % <-
        }{
            \node (v\index) at (\index cm,\index cm) {$\index$};
        }{
        };
    }
\end{tikzpicture}

\end{document}
  • Writing a loop which has {0,2,4,6,8} directly hard-wired as its range of the loop-variable is not an option for me, since the above is only a small illustrative example for the application I need this for, which has one large outer loop, and then would need several if-conditions of the form 'if-loop-variable-is-in-this-set-then-do-this', where several quite different "this set"s occur.

  • Briefly, I need to control a loop according to the boolean value of a query of the form 'is loopvariable in specifiedset'.

  • Of course, one could write a long logical-or of equality-tests, comparing \index with each of 0,2,4,6,8. Something more concise would be nice. I did not find it in a manual.

1
  • 1
    If you can build a marco \testset with the allowed values (e.g. \def\testset{0,2,4,6,8} or something else more complicated) then you can write \foreach \index in \testset {...;}. Aug 30 '17 at 8:44
6

Here is a simple solution. But you must declare and name your set before.

\documentclass{article}

\usepackage{etoolbox}
\usepackage{tikz}
\tikzset{
  def set elem/.code 2 args={\csdef{@sets@#1@#2}{}{}},
  def set/.style 2 args={
    def elem in@#1/.style={def set elem={#1}{##1}},
    def elem in@#1/.list={#2},
  }
}
\def\ifinset#1#2{\ifcsdef{@sets@#1@#2}}

\begin{document}
\tikzset{def set={myset}{2,4,6}}
\foreach \myval in {1,...,10}{
  \ifinset{myset}{\myval}{\myval{} in set\par}{\myval{} not in set\par}
}
\end{document}

Result:

1 not in set
2 in set
3 not in set
4 in set
5 not in set
6 in set
7 not in set
8 not in set
9 not in set
10 not in set
2
  • Comments, thought to be useful: (0) this works (for me), (1) the above code also works if '\myval' is replaced with two-dimensional variables of the form '\a/\b', (2) I am accepting this for the superficial reasons that lua seems not to exist on my current system and that this answer seems somehow better than the 'yet another loop'-solution in the other answer, though I would not be able to say precisely why it is better (and theoretically, the solutions are probably quite equivalent). Sep 1 '17 at 13:15
  • @PeterHeinig The list is looping through the elements passed to it.
    – A.Ellett
    Oct 15 '18 at 17:26
4

TikZ does not offer a test whether an item is in a list. It is also not possible to implement this test using the PGF macros because \foreach is not fully expandable.

Here is a Lua way to do this thing.

\documentclass{article}

\usepackage{tikz}
\usepackage{ifthen}

\directlua{
function has_value(t,v)
    for _,e in ipairs(t) do
        if e == v then
            tex.sprint("\noexpand\\boolean{true}")
            return
        end
    end
    tex.sprint("\noexpand\\boolean{false}")
end
}

\def\ifinset#1#2{\directlua{has_value({#1},#2)}}

\begin{document}

\begin{tikzpicture}
  \foreach \index in {0,1,...,101}{
    \ifthenelse{
      \ifinset{0,2,4,6,8}{\index}
    }{
      \node (v\index) at (\index cm,\index cm) {$\index$};
    }{
    };
  }
\end{tikzpicture}

\end{document}

If you don't mind looping over the test set as well, it is easily achievable in TikZ as well.

\documentclass{article}

\usepackage{tikz}
\usepackage{ifthen}

\begin{document}

\begin{tikzpicture}
  \foreach \index in {0,1,...,101}{
    \foreach \test in {0,2,4,6,8} {
      \ifthenelse{\equal{\index}{\test}}{
        \node (v\index) at (\index cm,\index cm) {$\index$};
      }{
        % false case
      }
    }
  }
\end{tikzpicture}

\end{document}
1

You can do it with the help of expl3; the second argument to \IsInTF can also be a macro expanding to a list.

\documentclass{article}

\usepackage{tikz}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\IsInTF}{mmmm}
 {
  \clist_if_in:xxTF { #2 } { #1 } { #3 } { #4 }
 }
\prg_generate_conditional_variant:Nnn \clist_if_in:nn { xx } { T,F,TF }
\ExplSyntaxOff

\begin{document}

\begin{tikzpicture}
  \foreach \index in {0,1,...,101}{
    \IsInTF{\index}{0,2,4,8} % <-
      {\node (v\index) at (\index cm,\index cm) {$\index$};}
      {};
  }
\end{tikzpicture}

\end{document}

enter image description here

0

This is an extension to Paul Gaborit's answer.

If you want to use his code to define a new command, the elements will only add to the set of previous commands. So it is required to remove the elements again, right after using the set. This can be done in a similar way, by using \cslet and setting the elements to \undefined. Here is a full MWE:

\documentclass{article}

\usepackage{etoolbox}
\usepackage{tikz}
\tikzset{
  def set elem/.code 2 args={\csdef{@sets@#1@#2}{}{}},
  def set/.style 2 args={
    def elem in@#1/.style={def set elem={#1}{##1}},
    def elem in@#1/.list={#2},
  },
  % these five lines are new to undefine elements:
  undef set elem/.code 2 args={\cslet{@sets@#1@#2}{\undefined}},
  undef set/.style 2 args={
    undef elem in@#1/.style={undef set elem={#1}{##1}},
    undef elem in@#1/.list={#2},
  }
}
\def\ifinset#1#2{\ifcsdef{@sets@#1@#2}}

\newcommand{\mycommand}[1]{%
  \tikzset{def set={myset}{#1}}
  \begin{tikzpicture}[
      every node/.style={minimum size=5mm, anchor=south west, inner sep=0pt},
      scale=0.5
  ]
  \foreach \myval in {1,...,20}{
    \ifinset{myset}{\myval}{
      \node [white, fill=green, font=\sffamily\bfseries] at (\myval, 0) {\myval};
    }{
      \fill [black] (\myval, 0) rectangle ++(1, 1);
    }
  }
  \end{tikzpicture}
  \tikzset{undef set={myset}{#1}} %<< new: required to remove the numbers again
}

\begin{document}
\thispagestyle{empty}
\mycommand{2,4,6,8,10,12,14,16,18,20}\par
\mycommand{3,6,9,12,15,18}\par
\mycommand{4,8,12,16,20}\par
\mycommand{5,10,15,20}\par
\mycommand{6,12,18}\par
\mycommand{7,14}\par
\mycommand{8,16}\par
\mycommand{9,18}\par
\mycommand{10,20}\par
\end{document}

working example image

In contrast, when you do not undefine the elements (comment out \tikzset{undef set={myset}{#1}}), the result looks like this:

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.