递归绘图,命令流

递归绘图,命令流

我正在尝试递归绘制 C k  x C k,其中 C k是康托集的第 k 次迭代。我有一个命令,它递归调用自身四次。如果我N作为参数传递#6,输出应该是 4 个小N方块。但它只适用于N= 1, 2。如果我做任何更高的操作,我只会得到 4 个方块,分布在四个角上。它们的大小合适,但数量不够。

显然,问题与有关\pgfmathtruncatemacro\C{#6 - 1}。作为测试,我\draw (0,0) circle (#6);在此行之前插入,然后在此行之后插入,输出并不总是相同的。似乎以\pgfmathtruncatemacro\C{#6 - 1}某种方式改变了的值#6(在相同的递归级别),但我不明白为什么。

\usepackage{tikz, pgf, ifthen}

\newcommand\cantorsquare[6]{ %x y x y coordinates, dissection coordinates, nesting
%the coordinates are listed in the same order as coordinates for a rectangle
    \ifthenelse{#6 = 0} %base case
    {
        \fill (#1, #2) rectangle (#3, #4);
    }
    {
        {
            %find the coordinates for the first smaller square
            \pgfmathsetmacro\A{#1 + (1 - #5) * (#3 - #1) / 2}
            \pgfmathsetmacro\B{#2 + (1 - #5) * (#4 - #2) / 2}
            \pgfmathtruncatemacro\C{#6 - 1}
            \cantorsquare{#1}{#2}{\A}{\B}{#5}{\C}
        }
        {
            \pgfmathsetmacro\A{#1 + (1 - #5) * (#3 - #1) / 2}
            \pgfmathsetmacro\B{#4 - (1 - #5) * (#4 - #2) / 2}
            \pgfmathtruncatemacro\C{#6 - 1}
            \cantorsquare{#1}{\B}{\A}{#4}{#5}{\C}
        }
        {
            \pgfmathsetmacro\A{#3 - (1 - #5) * (#3 - #1) / 2}
            \pgfmathsetmacro\B{#2 + (1 - #5) * (#4 - #2) / 2}
            \pgfmathtruncatemacro\C{#6 - 1}
            \cantorsquare{\A}{#2}{#3}{\B}{#5}{\C}
        }
        {
            \pgfmathsetmacro\A{#3 - (1 - #5) * (#3 - #1) / 2}
            \pgfmathsetmacro\B{#4 - (1 - #5) * (#4 - #2) / 2}
            \pgfmathtruncatemacro\C{#6 - 1}
            \cantorsquare{\A}{\B}{#3}{#4}{#5}{\C}
        }
    }
}

\begin{document}
    \begin{tikzpicture} \cantorsquare{0}{0}{5}{5}{0.3}{3} \end{tikzpicture}
\end{document}

答案1

我同意迈克尔的分析:这是一个扩展问题。当你传递\C第六个元素时,它#6会在宏主体中被替换,\C因此如果\C更改,那么也会更改最终的的值#6。具体来说,当您\pgfmathtruncatemacro\C{#6 - 1}这样做时,您正在重新定义#6

话虽如此,我不确定扩展哪里出了问题,因为您已正确分组了递归调用,因此\C一次迭代中的修改不会影响其他迭代。此外,当我运行您的代码时,我得到了我期望看到的答案。所以我怀疑这是旧版本的 PGF 的问题,它进行了全局分配,而它应该进行本地分配。您使用的是哪个版本的 PGF?

我发现 LaTeX3 有很多有用的东西可以帮助解决这些扩展问题。特别是,可以确保价值传递的是宏的参数,而不是宏本身。所以这是用 LaTeX3 重写的代码。通过传递值而不是宏,我可以避免对递归调用进行分组。

\documentclass{article}
%\url{http://tex.stackexchange.com/q/136907/86}
\usepackage{tikz}
\usepackage{xparse}

\ExplSyntaxOn

\cs_new_nopar:Npn \cantor_sq:nnnnnn #1#2#3#4#5#6
{
  \int_compare:nTF {#6 = 0}
  {
    \fill (\fp_to_decimal:n {#1},\fp_to_decimal:n {#2}) rectangle (\fp_to_decimal:n {#3},\fp_to_decimal:n {#4});
  }
  {
    %find the coordinates for the first smaller square
    \fp_set:Nn \l_tmpa_fp {#1 + (1 - #5) * (#3 - #1) / 2}
    \fp_set:Nn \l_tmpb_fp {#2 + (1 - #5) * (#4 - #2) / 2}
    \int_set:Nn \l_tmpa_int {#6 - 1}
    \cantor_sq:nnVVnV {#1}{#2} \l_tmpa_fp \l_tmpb_fp {#5} \l_tmpa_int

    \fp_set:Nn \l_tmpa_fp {#1 + (1 - #5) * (#3 - #1) / 2}
    \fp_set:Nn \l_tmpb_fp {#4 - (1 - #5) * (#4 - #2) / 2}
    \int_set:Nn \l_tmpa_int {#6 - 1}
    \cantor_sq:nVVnnV {#1} \l_tmpb_fp \l_tmpa_fp {#4}{#5} \l_tmpa_int

    \fp_set:Nn \l_tmpa_fp {#3 - (1 - #5) * (#3 - #1) / 2}
    \fp_set:Nn \l_tmpb_fp {#2 + (1 - #5) * (#4 - #2) / 2}
    \int_set:Nn \l_tmpa_int {#6 - 1}
    \cantor_sq:VnnVnV \l_tmpa_fp {#2}{#3} \l_tmpb_fp {#5} \l_tmpa_int

    \fp_set:Nn \l_tmpa_fp {#3 - (1 - #5) * (#3 - #1) / 2}
    \fp_set:Nn \l_tmpb_fp {#4 - (1 - #5) * (#4 - #2) / 2}
    \int_set:Nn \l_tmpa_int {#6 - 1}
    \cantor_sq:VVnnnV \l_tmpa_fp \l_tmpb_fp {#3}{#4}{#5} \l_tmpa_int
  }
}

\cs_generate_variant:Nn \cantor_sq:nnnnnn {nnVVnV,nVVnnV,VnnVnV,VVnnnV}

\DeclareDocumentCommand \cantorsquare { m m m m m m }
{
  \cantor_sq:nnnnnn {#1}{#2}{#3}{#4}{#5}{#6}
}

\ExplSyntaxOff

\begin{document}
    \begin{tikzpicture}
 \cantorsquare{0}{0}{5}{5}{0.3}{3}
 \end{tikzpicture}

    \begin{tikzpicture}
 \cantorsquare{0}{0}{5}{5}{0.3}{6}
 \end{tikzpicture}
\end{document}

康托集

从更大的放大图可以看出,小矩形看起来不均匀是渲染的瑕疵。

Cantor 设置为更大缩放

答案2

我不知道您使用的软件包的详细信息,因此我很难下结论,但您似乎被这样一个事实所困扰:作为参数传递的宏在执行调用时不会被展开,而只有在处理它们出现的替换文本时才会展开。这可能会破坏您的递归。要验证这一点,您可以使用 执行代码\tracingmacros=1

您可以使用寄存器来强制扩展,从而取代您的使用

\cantorsquare{#1}{#2}{\A}{\B}{#5}{\C}

经过

\begingroup
\let\cantorsquare=0\relax % Freeze expansion in edef
\count0=\A\relax
\count2=\B\relax
\count4=\C\relax
\toks0={#1}%
\toks2={#2}%
\toks4={#5}%
\edef\next{%
  \cantorsquare{\the\toks0}{\the\toks2}{\the\count0}{\the\count2}{\the\toks4}{\the\count4}%
}%
\expandafter
\endgroup
\next

它准备一个“调用”,cantorsquare其中\A等被替换为它们的替换文本 — 假设这些是数字。您应该相应地编辑四个调用中的每一个。

请注意,TeX 是一种非常特殊的计算机语言,因为它不与函数之类的东西一起工作,而是为您提供编写程序后续内容的可能性——这就是宏的用途。如果您想用 TeX 编写复杂的程序,那么了解寄存器\edef等会容易得多。

这有帮助吗?

编辑:顺便说一句,如果你对用程序生成图形感兴趣,我强烈建议你看看 METAPOST!这是一个惊人的软件——编程起来和 TeX 一样有趣。

答案3

编辑:下面添加了lualatex版本。

正如已经指出的那样,问题在于扩展,并且很容易纠正。然而,这里有一个不同的解决方案,需要最新的 PGF/TikZ CVS 版本

\documentclass[border=0.125cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{math}

\begin{document}

\begin{tikzpicture}

\tikzmath{
    function cantorsquare(\x, \y, \s, \f, \d) {
        if (\d == 0) then {
            { \fill (\x, \y) rectangle ++(\s, \s); };
        } else {
            \u1 = \f*\s;
            \u2 = (1-\f)*\s;
            cantorsquare(\x, \y, \u1, \f, \d-1);
            cantorsquare(\x+\u2, \y, \u1, \f, \d-1);
            cantorsquare(\x, \y+\u2, \u1, \f, \d-1);
            cantorsquare(\x+\u2, \y+\u2, \u1, \f, \d-1);
        };
    };
    \S = 5;
    for \d in {0,...,5}{
        \x = int(\d/3) * (\S+1);
        \y = mod(\d,3) * (\S+1);
        { \draw (\x-0.1, -\y-0.1) rectangle ++(\S+0.2,\S+0.2); };
        cantorsquare(\x, -\y, \S, 1/3, \d);
    };
}

\end{tikzpicture}

\end{document}

在此处输入图片描述

下面是使用lualatex它不需要最新的 CVS,并且很多快点。

\documentclass[border=0.125cm]{standalone}
\usepackage{tikz}

\directlua{
function cantorsquare(x, y, s, f, d)
    local u1, u2
    if d == 0 then
        tex.print("\noexpand\\fill (" .. x .. "," .. y .. ") rectangle ++(" .. s .. "," .. s .. ");")
    else
        u1 = f*s
        u2 = (1-f)*s;
        cantorsquare(x, y, u1, f, d-1)
        cantorsquare(x+u2, y, u1, f, d-1)
        cantorsquare(x, y+u2, u1, f, d-1)
        cantorsquare(x+u2, y+u2, u1, f, d-1)
    end
end
}

\begin{document}

\begin{tikzpicture}

\foreach \d [evaluate={\S=5; \x=int(\d/3)*(\S+1); \y=mod(\d,3)*(\S+1);}] in {0,...,5}{
    \draw (\x-0.1, -\y-0.1) rectangle ++(\S+0.2,\S+0.2); 
    \directlua{cantorsquare(\x, -\y, \S, 1/3, \d)}
}
\end{tikzpicture}

\end{document}

结果和以前一样。

答案4

我不知道这个的具体规格事物但这里有一个递归尝试append after command(以前的版本使用过path picture但只适用于矩形)。

代码

\documentclass[tikz]{standalone}
\usetikzlibrary{calc}
\tikzset{
  if/.code n args={3}{% forest.sty
    \pgfmathparse{#1}%
    \ifnum\pgfmathresult=0 \pgfkeysalso{#3}\else\pgfkeysalso{#2}\fi},
  cantorsquare/.style n args={3}{% #1 = current leve, #2 = max level, #3 = factor
    if={#1==#2}{cantorsquare end}{cantorsquare go on={#1+1}{#2}{#3}}},
  every cantorsquare/.style={shape=rectangle, outer sep=+0pt, ultra thin},
  cantorsquare end/.style={every cantorsquare, fill},
  cantorsquare go on/.style n args={3}{
    every cantorsquare,
    append after command={
      \pgfextra{\let\myTikzlastnode\tikzlastnode}
      let \p{@dim}=($(\myTikzlastnode.north east)-(\myTikzlastnode.south west)$) in
        [next nodes/.style={inner sep=+0pt, outer sep=+0pt, minimum width=(#3)*\x{@dim},
                                   minimum height=(#3)*\y{@dim}, cantorsquare={#1}{#2}{#3}}]
        node [next nodes, above right] at (\myTikzlastnode.south west) {}
        node [next nodes, above left]  at (\myTikzlastnode.south east) {}
        node [next nodes, below right] at (\myTikzlastnode.north west) {}
        node [next nodes, below left]  at (\myTikzlastnode.north east) {}}}}
\begin{document}
\foreach \lev in {1, ..., 5, 4, 3, 2}{
\begin{tikzpicture}
\node[cantorsquare/.expanded={1}{\lev}{.3333}, minimum size=2cm] {};
\end{tikzpicture}}
\foreach \lev in {1, ..., 5, 4, 3, 2}{
\begin{tikzpicture}[every cantorsquare/.append style={shape=circle}, 
                    every cantorsquare/.append style=draw]
\node[cantorsquare/.expanded={1}{\lev}{.3333}, minimum size=2cm] {};
\end{tikzpicture}}
\end{document}

输出

在此处输入图片描述 在此处输入图片描述

相关内容