8

I need to draw many similar complicated colored shapes; I made a macro for them, and it works, but to call it I seemingly have to place several color names in double quotes. Can I avoid this?

The example I produced is a simplified one, what I need is different but the example (hopefully) gives the idea.

\documentclass{amsart}

\usepackage{tikz}

\def\sillyexample#1#2{
 \newcount\p
 \foreach\i in {#1}
 {
   \foreach\j in {1,...,\i}
   {
     \pgfmathparse{{#2}[\j+\the\p-1]}
     \node [\pgfmathresult] at (\j+\the\p,0) {\i};
   }
   \global\advance\p by \i
 }
}

\begin{document}

 \begin{tikzpicture}
  \sillyexample{2,3,1}{"red","blue","green","green","cyan","blue"}
 \end{tikzpicture}

\end{document}

As I said this works, the result

enter image description here

is as expected, the only thing I want is whether I could avoid all these double quotes around color names in the call.

Well, also - since I am asking anyway - can my code be improved in any other way?

  • Not if you want to access a nonnumeric array via pgfmathparse. – percusse Jan 27 '15 at 16:33
  • @percusse Certainly I don't insist on using pgfmathparse or anything else - the only thing I want is to achieve coloring of a given succession of blocks of various shapes with a given sequence of colors – მამუკა ჯიბლაძე Jan 27 '15 at 16:36
  • You could use the xstring package for arrays. See tex.stackexchange.com/questions/215563/… – John Kormylo Jan 27 '15 at 17:30
  • @JohnKormylo Sorry I looked at the link you provided and also at the xstring documentation but could not figure out how to use it here. Could you explain a bit? – მამუკა ჯიბლაძე Jan 27 '15 at 18:06
6

The LaTeX kernel has features for working on comma separated lists:

\documentclass{amsart}
\usepackage{tikz}

\newcount\p

\def\sillyexample#1#2{%
 \p=0
 \addquotes\sillyexampleargtwo{#2}%
 \foreach\i in {#1}%
 {%
  \foreach\j in {1,...,\i}%
   {%
    \pgfmathparse{{\sillyexampleargtwo}[\j+\the\p-1]}%
    \node [\pgfmathresult] at (\j+\the\p,0) {\i};%
   }%
   \global\advance\p by \i
 }%
}
\makeatletter
\newcommand\addquotes[2]{%
  \def#1{\@gobble}%
  \@for\next:=#2\do{%
    \edef#1{#1,\string"\next\string"}%
  }%
}
\makeatother


\begin{document}

\begin{tikzpicture}
\sillyexample{2,3,1}{red,blue,green,green,cyan,blue}
\end{tikzpicture}

\end{document}

The \addquotes macro goes through the given list and builds a new one with each item between double quotes.

Be always careful and place \newcount instructions outside definitions. With your code, a new counter would be allocated each time the \sillyexample macro is called. Also \p is not a good name, actually.

5

Here I just create a recursive helper routine \addquotes.

\documentclass{amsart}
\usepackage{tikz}

\def\sillyexample#1#2{
 \newcount\p
 \foreach\i in {#1}
 {
   \foreach\j in {1,...,\i}
   {
     \pgfmathparse{{\addquotes#2,\relax}[\j+\the\p-1]}
     \node [\pgfmathresult] at (\j+\the\p,0) {\i};
   }
   \global\advance\p by \i
 }
}
\def\addquotes#1,#2\relax{"#1",\if\relax#2\relax\else\addquotes#2\relax\fi}

\begin{document}

 \begin{tikzpicture}
  \sillyexample{2,3,1}{red,blue,green,green,cyan,blue}
 \end{tikzpicture}

\end{document}
  • 1
    There is an upper limit to the recursion depth, but I can't recall if it is 256 or 65536, but hopefully you won't be specifying that large an array. Other than that, it of course requires comma separation in the list. But it should be robust. – Steven B. Segletes Jan 27 '15 at 18:39
  • 1
    @მამუკაჯიბლაძე I would add that I learned recursion techniques from one of David Carlisle's posts long ago. It was a "game changer" for me. I am constantly amazed at how many things can be done quickly with a simple recursion macro. – Steven B. Segletes Jan 27 '15 at 18:45
  • 1
    @მამუკაჯიბლაძე You can define \def\addquotes#1,{\ifx,#1,\else"#1",\expandafter\addquotes\fi} and replace the {\addquotes#2,\relax} by {\addquotes#2,,}. Now this is not recursion but normal loop, because the next call of \addquotes is at the really end of the previous call. – wipet Jan 28 '15 at 16:15
  • 2
    Yes. The number of items in the processed list is unlimited when my code is used. Because this is not true recursion. – wipet Jan 29 '15 at 15:44
  • 2
    @cfr On the surface, one could call both wipet's and my approaches "recursive", because the routine is self referential. However, wipet's point might seem like a technicality, but is actually a point of significance. If \addquotes calls itself before it ends (my approach), residue from each call is on "the stack" until the final exit. It is thus subject to stack limitations. wipet points out that if \addquotes finishes, and then calls itself without argument (his approach), there are no residual pushes on the stack. Thus the approach is not stack limited. – Steven B. Segletes Feb 2 '15 at 1:11
5

Without using any extra macros or packages, here is a solution which runs in linear time O(k), i.e. linear in k, where k is the number of colors (equal to 6 in your post). It is all about conditions. The two conditions that I use here are:

\ifnum \k > \p{
\ifnum \k < \numexpr\num+\p+1 \relax 
...

These conditions precisely pick only the values required for pairing each number with its color. The following results are obtained by running (in order):

\lesssillyexample{2,3,1}{red,blue,green,green,cyan,blue}
\lesssillyexample{5,3,2}{red,blue,green,green,cyan,blue,red,blue,green,green}
\lesssillyexample{3,2,3,1,2}{red,blue,green,green,cyan,blue,red,blue,green,green,red}

enter image description here

And here is the complete code:

\documentclass{amsart}
\usepackage{tikz}
\newcount\p
\def\lesssillyexample#1#2{
 \p=0
 \foreach \num in {#1}
 {
  \foreach [count=\k]\kthcolor in {#2}
  {
   \ifnum \k > \p{
    \ifnum \k < \numexpr\num+\p+1 \relax 
     \node at(\k,0)[\kthcolor]{\num};
    \fi}
   \fi
  }
  \global\advance\p by \num
 }
}
\begin{document}
 \begin{tikzpicture}
  \lesssillyexample{2,3,1}{red,blue,green,green,cyan,blue}
 \end{tikzpicture}
\end{document}
  • This is I think the perfect solution! I want to accept this one, except that I also like very much another one I already accepted because of that recursion trick. Do you know how to accept two answers? :) – მამუკა ჯიბლაძე Jan 28 '15 at 6:45
  • Here on TeX.SE, when two answers both work, favour the simpler one. This is the rule. – AboAmmar Jan 28 '15 at 10:00
  • There's no need for \the\numexpr in that context: actually \ifnum\k<\numexpr\num+\p+1\relax is better than with \the because it avoids two useless expansions (and also premature expansion of \node). – egreg Feb 1 '15 at 21:03
  • 1
    I tend to think the rule is 'accept the one which helped you most', insofar as there is a rule. But, certainly going for the simpler one is recommended, ceteris paribus. – cfr Feb 1 '15 at 21:37
3

I had to modify \getdata a bit to get it to work as a tikz parameter.

\documentclass{amsart}
\usepackage{tikz}
\usepackage{xstring}

\newcounter{comma}
\newcommand{\colorstr}{}% reserve name
\newcommand{\getcolor}[2][1]% #1 = index, #2 = array name
{\ifnum#1=1\relax\StrBefore{#2}{,}[\colorstr]%
\else\setcounter{comma}{#1}\addtocounter{comma}{-1}%
\StrCount{#2}{,}[\colorstr]%
\ifnum\value{comma}=\colorstr\relax\StrBehind[\thecomma]{#2}{,}[\colorstr]%
\else\StrBetween[\thecomma,#1]{#2}{,}{,}[\colorstr]%
\fi\fi}

\def\sillyexample#1#2{
 \newcount\p
 \foreach\i in {#1}
 {
   \foreach\j in {1,...,\i}
   {
     \pgfmathparse{int(\j+\the\p-1)}
     \getcolor[\pgfmathresult]{#2}
     \node[\colorstr] at (\j+\the\p,0) {\i};
   }
   \global\advance\p by \i
 }
}

\begin{document}

 \begin{tikzpicture}
  \sillyexample{2,3,1}{red,blue,green,green,cyan,blue}
 \end{tikzpicture}

\end{document}
2

The above comment by John Kormylo, although so far I don't understand it, gave me a hint. He provided a link to an answer to another question; another answer to that question inspired this solution:

\documentclass{amsart}

\usepackage{tikz}

\def\lesssillyexample#1#2{
 \newcount\p
 \foreach\i in {#1}
 {
  \foreach\j in {1,...,\i}
  {
   \foreach[count=\k] \kthcolor in {#2}
   {
    \ifnum \k = \the\numexpr\j+\the\p\relax 
     \node [\kthcolor] at (\k,0) {\i};
     \breakforeach
    \fi
   }
  }
  \global\advance\p by \i
 }
}

\begin{document}

 \begin{tikzpicture}
  \lesssillyexample{2,3,1}{red,blue,green,green,cyan,blue}
 \end{tikzpicture}

\end{document}

This works, but at the expense of, instead of picking the entry from the required place of the color array, each time starting to scan the array from the beginning until that place is reached, which is very inefficient and inelegant. So although I post this as an answer, I am not going to accept it. Maybe somebody can improve on it or propose something different.

  • Notice that you don't need that counter. Use TikZ option count on the foreach spin. – percusse Jan 27 '15 at 19:30
  • @percusse but count only advances by one at each step, and I need to advance it by the ith member of the list – მამუკა ჯიბლაძე Jan 27 '15 at 21:50
  • My mistake. The count was not in your original case in the question. – percusse Jan 27 '15 at 22:33
  • @percusse right, this new \k advances by one, but \p jumps by \i – მამუკა ჯიბლაძე Jan 27 '15 at 22:35
1

I think I've found the ultimate solution :)

\documentclass{amsart}

\usepackage{tikz}

\newcount\positi
\newcount\cumuli

\def\finalexample#1#2{
  \cumuli=0
  \positi=0
  \foreach\kthcolor[count=\k] in {#2}{
    \ifnum\k>\cumuli
      \pgfmathparse{{#1}[\positi]}
      \let\curri\pgfmathresult
      \advance\cumuli by\curri
      \advance\positi by1
    \fi
    \node[\kthcolor] at (\k,0) {\curri};
  }
}

\begin{document}

  \begin{tikzpicture}
    \finalexample{2,3,1}{red,blue,green,green,cyan,blue}
  \end{tikzpicture}

\end{document}

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.