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?

6 Answers6

6

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
  • Well I heard that recursion usually strains resources; but this must be the case when it is harmless... – მამუკა ჯიბლაძე Jan 27 '15 at 18:49
  • @მამუკაჯიბლაძე If you formulate the recursion improperly, then it certainly strains resources as it enters an endless loop 8^) – Steven B. Segletes Jan 27 '15 at 18:50
  • :)) no but seriously, as a rule recursive things require exponential time and space, no? So they must be used only in cases when the input size is obviously small, like here – მამუკა ჯიბლაძე Jan 27 '15 at 18:56
  • @მამუკაჯიბლაძე I think it depends on the recursion. Things like bubble sorts grow at a rate that is more than linear. In this case, however, the recursion is merely of the form -digest an input, -process the input, -if not done, do it again. – Steven B. Segletes Jan 27 '15 at 19:14
  • 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
  • @wipet I would still call it recursion, since \addquotes calls upon itself (in both cases, with a shorter argument, until exhausted). – Steven B. Segletes Jan 28 '15 at 16:17
  • @wipet Nonetheless, I find the coding of your "recursion" style interesting, and will study it more (apologies in advance if I steal it for future use 8^). – Steven B. Segletes Jan 28 '15 at 16:22
  • 1
    @wipet One of the reasons I like using \relax as a recursion delimiter is that it is not a command likely to appear in a user argument. Thus, the macro is less likely to be led astray by a typo or user assumption. Either of our macros works for the proposed argument list, but if a ,, is introduced in the middle of the list (by accident or by assumption), my macro takes it as a "black" argument, whereas yours breaks. – Steven B. Segletes Jan 28 '15 at 16:34
  • 1
    You can use {\addquotes#2,\end,} and you can test \ifx\end#1\else... if you are afraid of ,,. I can be afraid of \relax in the argument list too. But the main difference between your and my recursion is that you call \addquotes in the middle of the macro body, i.e. the return pointers and data have to be saved to the stack of this recursion. But if the macro call is at the end of the macro body, TeX does not push new data to the stack but simply replaces previous macro body by the next. From this point of view my solution isn't real recursion. It is used for loop implementation. – wipet Jan 29 '15 at 07:09
  • @wipet Thanks for that explanation. If I understand you correctly, your approach would not be subject to any recursion depth limitation? If so, that is good to know. – Steven B. Segletes Jan 29 '15 at 12:42
  • 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
  • 1
    What makes something count as 'recursion'? (I know what a recursive definition is in logic/maths, but I'm not quite sure how that sense relates to this one?) – cfr Feb 01 '15 at 21:35
  • 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 02 '15 at 01:11
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.

egreg
  • 1,121,712
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}
AboAmmar
  • 46,352
  • 4
  • 58
  • 127
  • 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 06: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 01 '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 01 '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}
John Kormylo
  • 79,712
  • 3
  • 50
  • 120
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.

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}