模仿键值对的 TeX 参数处理

模仿键值对的 TeX 参数处理

我彻底困惑了这个答案是关于如何在圆周上生成 n 个点并连接所有点,同时对图像大小进行限制因为我不明白参数处理是如何进行的?

宏声明为

\def\drawconnectvertices[num vertex=#1, circle radius=#2] at (#3);{%
    \pgfmathtruncatemacro\vertices{#1}
    \pgfmathsetmacro\circleradius{#2}
    ...
}

并被调用为:

\drawconnectvertices[num vertex=6, circle radius=3] at (0,0);

当我读到这个时,我看到#1num vertex=6, circle radius=3,所以将\pgfmathtruncatemacro\vertices{#1}是:

\pgfmathtruncatemacro\vertices{num vertex=6, circle radius=3}

并且应该会失败。

TeX 在定义宏方面似乎比我想象的更加灵活。所以我的问题是:

  • 这是定义宏的可接受方式吗?如果不是,那么应该如何定义它们。

  • 一个问题是第一个参数实际上不是可选的。但除此之外,这种定义宏的方式还存在其他问题吗?

代码:

\documentclass{article}% Extracted from https://tex.stackexchange.com/a/88312/4301
\usepackage{tikz}
\usetikzlibrary{shapes.geometric} % required for the polygon shape

\def\drawconnectvertices[num vertex=#1, circle radius=#2] at (#3);{%
\pgfmathtruncatemacro\vertices{#1}
\pgfmathsetmacro\circleradius{#2}
\pgfmathsetmacro\halfcircleradius{\circleradius/2}
\draw[blue] (#3) circle (\halfcircleradius cm) node[regular polygon, regular polygon sides=\vertices, minimum size=\circleradius cm, draw=none, name={vertex set}] {};
\foreach \x in {1,...,\vertices}{
    \node[draw,circle, inner sep=1pt,blue, fill=blue] at (vertex set.corner \x) {};
}

\foreach \x in {1,...,\vertices}{
    \foreach \y in {\x,...,\vertices}{
            \draw[ultra thin, red] (vertex set.corner \x)--(vertex set.corner \y);
    }
}
}

\begin{document}
\begin{tikzpicture}
    \drawconnectvertices[num vertex=6, circle radius=3] at (0,0);
\end{tikzpicture}
\end{document}

答案1

这样的定义存在多个问题。该\def指令的语法如下:

\def<cs><parameter text><left brace><balanced text><right brace>

其中<cs>,需要定义的控制序列或活动字符为;<left brace>代表<right brace>明确的括号(分别具有类别代码 1 和 2 的字符标记);<balanced text>是具有匹配的显式括号的任意标记列表,其可以包含也出现在中的任何#xx从 1 到 9 的数字)<parameter text>

这里最重要的当然是<parameter text>,它可以非常通用,因为唯一的限制本质上是其中不允许使用明确的括号,并且“参数标记”#1#2必须按顺序出现。

如果参数文本以不同于 的内容开头#1,则这些标记必须跟在宏名称后面。如果参数标记后面紧跟着另一个参数标记,则表示未限定参数,否则它代表一个分隔符以及那些代币必须在调用宏时以相同的顺序出现:控制序列名称必须相同,对于字符,字符代码和类别代码必须匹配。参数将是从前一个分隔符(或参数,如果它是无分隔符)到分隔符标记的所有内容,但这也会导致一个与括号相平衡的标记列表。

例如,让我们看看

\def\foo\bar#1x\baz#2#3\end{#1--#2--#3}

我们有

  1. “宏分隔符”\bar

  2. 第一个参数是从\bar(排除)到第一个实例的任意内容x\baz

  3. 第二个参数是“未限定的”,因此它将是下一个标记,或者当这是左括号时,所有平衡组

  4. 第三个参数可以是第二个参数末尾到\end

调用示例

  1. \foo\bar XYx\baz ZABC\end

    我们有#1=XY#2=Z#3=ABC

  2. \foo\bar XYx\baz {SOME}ABC\end

    我们有#1=XY,,#2=SOME#3=ABC括号从无界参数中去掉)

  3. \foo\bar {XYx\baz}x\baz ZABC\end

    我们有,,,#1=XYx\baz因为第一对位于第一级括号中#2=Z#3=ABCx\baz

  4. \foo\bar XYx\baz Z\end

    我们有#1=XY,,#2=Z并且#3为空。

还有很多其他的例子可以想出,但最好参考 TeXbook(第 20 章)或 TeX by Topic(第 11 章)


让我们看看你提供的代码会发生什么

\def\drawconnectvertices[num vertex=#1, circle radius=#2] at (#3);{...}

<replacement text>与本次讨论无关)。

  1. 参数文本由“宏分隔符”组成:

     [num vertex=
    
  2. 第一个参数由

     , circle radius=
    
  3. 第二个参数由

     ] at (
    
  4. 第三个参数由

     );
    

因此,像这样的调用

\drawconnectvertices[num vertex=6, circle radius=3] at (0,0);

将会吸收

#1 = 6
#2 = 3
#3 = 0,0

在此过程中,分隔符标记将被丢弃。

什么情况会出错?

在选项列表中,通常会省略逗号后的空格;因此用户可能会输入

\drawconnectvertices[num vertex=6,circle radius=3] at (0,0);

这会导致 TeX 找不到第一个参数的正确分隔符,并继续从文件读取直到文件末尾。嗯,直到文件末尾,因为某些特定位置会告诉 TeX 出了问题,特别是一个\par标记。在这种情况下,错误消息将是:

! Paragraph ended before \drawconnectvertices was complete.
<to be read again> 
                   \par 

这似乎没有什么帮助。

同样的情况也会发生

\drawconnectvertices[num vertex=6, circle radius=3] at (0,0) ;

因为分号前面有空格;这可能会更加糟糕,因为其他一些后续的 TikZ 指令可能会有这样的形式(1,2);全部0,0从到 的文本1,2将被视为 的第三个参数\drawconnectvertices

如何做得更好?

使用适当的 TikZ 约定来定义这样的宏:定义键并使用 PGF/TikZ 提供的基础结构(我不是这方面的专家,肯定有人会帮助找到更强大的定义)。当然,安全的定义应该是:

\def\drawconnectvertices#1#2#3{%
  \pgfmathtruncatemacro\vertices{#1}
  \pgfmathsetmacro\circleradius{#2}
  \pgfmathsetmacro\halfcircleradius{\circleradius/2}
  \draw[blue] (#3) circle (\halfcircleradius cm) node[regular polygon, regular polygon sides=\vertices, minimum size=\circleradius cm, draw=none, name={vertex set}] {};
  \foreach \x in {1,...,\vertices}{
    \node[draw,circle, inner sep=1pt,blue, fill=blue] at (vertex set.corner \x) {};
  }
  \foreach \x in {1,...,\vertices}{
    \foreach \y in {\x,...,\vertices}{
            \draw[ultra thin, red] (vertex set.corner \x)--(vertex set.corner \y);
    }
  }
}

使用如下调用

\drawconnectvertices{6}{3}{0,0}

但当然,更像 TikZish 的版本会更有吸引力,也更清晰。


更加 TikZish 的标记

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc,shapes.geometric}

\makeatletter
\tikzset{
  num vertices/.code=\def\dcv@vertices{#1},
  num vertices/.default=3,
  circle radius/.code=\def\dcv@circleradius{#1},
  circle radius/.default=1,
}
\newcommand\drawconnectvertices[1][]{%
  \tikzset{num vertices, circle radius, #1}%
  \dcv@auxi}
\def\dcv@auxi#1at#2;{\dcv@auxii#2;}
\def\dcv@auxii#1(#2)#3;{\def\dcv@origin{#2}\dcv@main}
\def\dcv@main{%
  \pgfmathtruncatemacro\dcv@vertices{\dcv@vertices}
  \pgfmathsetmacro\dcv@circleradius{\dcv@circleradius}
  \pgfmathsetmacro\dcv@halfcircleradius{\dcv@circleradius/2}
  \draw[blue] (\dcv@origin) circle (\dcv@halfcircleradius cm) 
     node[regular polygon, 
          regular polygon sides=\dcv@vertices, 
          minimum size=\dcv@circleradius cm, 
          draw=none, 
          name={vertex set}] {};
  \foreach \x in {1,...,\dcv@vertices}{
    \node[draw,circle, inner sep=1pt,blue, fill=blue] at (vertex set.corner \x) {};
  }
  \foreach \x in {1,...,\dcv@vertices}{
    \foreach \y in {\x,...,\dcv@vertices}{
            \draw[ultra thin, red] (vertex set.corner \x)--(vertex set.corner \y);
    }
  }
}
\makeatother

\begin{document}
\begin{tikzpicture}
\drawconnectvertices[num vertices=6,circle radius=3] at (0,0);
\end{tikzpicture}

\begin{tikzpicture}
\drawconnectvertices[circle radius=3] at (0,0);
\end{tikzpicture}

\begin{tikzpicture}
\drawconnectvertices[num vertices=6] at (0,0);
\end{tikzpicture}
\end{document}

在此处输入图片描述

我确信 TikZ 具有使其更加强大的功能。

答案2

这是一个键值接口。我使用斯基瓦尔包而不是鍵盤只是因为我想预设按键。

\documentclass{article}
\usepackage{skeyval}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\makeatletter
% Spurious spaces will appear in the following methods in horizontal
% mode, but TikZ doesn't mind them. The star (*) form of \directkeys
% assumes every code line ends with comment sign.
\directkeys*{
  .family=graph,
  .holder prefix=cvt@,
  .initialize keys after define,
  .define keys={
    % Multiple ordinary keys with default '6' and common callback:
    .ord/{num vertices,number of vertices,vertices}/6/
      \skvensureinteger{vertices}{#1}
      \def\cvt@vertices{#1}
    ,
    .ord/{radius,circle radius}/2/\def\cvt@radius{#1},
    % Command keys with default '0' and no callbacks:
    .cmd/{x,y}/0
  },
  % Set preset list, in case some keys are absent when calling \drawgraph:
  .preset keys={vertices,radius,x,y}
}
\newcommand*\drawgraph[1][]{%
  \directkeys{
    .family=graph,.set keys={#1}
  }
  \pgfmathsetmacro\halfcircleradius{\cvt@radius/2}
  \draw[blue] (\cvt@x,\cvt@y) circle (\halfcircleradius cm)
    node [regular polygon, regular polygon sides=\cvt@vertices,
          minimum size=\cvt@radius cm, draw=none, name={vertex set}]{};
  \foreach \x in {1,...,\cvt@vertices}{
    \node[draw,circle,inner sep=1pt,blue,fill=blue] at (vertex set.corner \x){};
  }
  \foreach \x in {1,...,\cvt@vertices}{
    \foreach \y in {\x,...,\cvt@vertices}{
      \draw[ultra thin, red] (vertex set.corner \x)--(vertex set.corner \y);
    }
  }
}
\makeatother

\begin{document}
\begin{tikzpicture}
\drawgraph[number of vertices=6, circle radius=3, x=0, y=0]
\drawgraph[number of vertices=8, circle radius=3, x=4, y=0]
\end{tikzpicture}
\par\bigskip

\begin{tikzpicture}
% Use default values of positions:
\drawgraph[vertices=8, radius=3]
% Use default values of vertices and radius:
\drawgraph[x=4, y=0]
\end{tikzpicture}
\par\bigskip

\begin{tikzpicture}
% Use default values of all keys:
\drawgraph
\end{tikzpicture}
\end{document}

在此处输入图片描述

图表示例位于http://www.texample.net/tikz/examples/tag/graphs/

相关内容