如何使用 TikZ 算术引擎编写简单函数?
我喜欢使用 TikZ 来“编程”我的图片,只需编写一次代码即可重复使用,获得精确的坐标和交点,只需更改一个数字,整个图片就会自动更新依赖于该数字的所有内容。这真是太棒了。
不过,也许是因为我非常习惯使用更传统的编程语言,而且我思考以一种更加程序化的方式(而不是“宏扩展”是”方式),当我需要一些基本的东西(例如if
或while
循环)时,我很快就会感到沮丧。我掌握了 TikZ\foreach
命令,它工作得很好;但为什么没有定义if
和的类似命令while
?(或者有,但我错过了它们?)
你可能会向我指出其他软件包做定义这样的命令,但我发现编写正确的代码来在 TikZ 算术表达式和字符串、标记、(未)扩展的宏或这些包在其条件中实际比较的任何东西之间切换并不是一件容易的事(而且很令人头疼)。
我认为以下几点是必须具备的:
\ifmath{expr}{true}{false}
— 评估expr
如果为真(例如非 0),则继续执行该部分的代码true
;否则继续执行该false
部分。\whilemath{expr}{code}
— 重复执行,code
直到expr
产生真值。
我已经写好了这些内容的版本,但我想看看你能想出什么。
现在,这是最基本的,但我真正能做的是轻松定义也可以在 中使用的新函数expr
。例如,假设我想编写一个很小的函数,它只接受两个数字并返回它们的最大公约数。这是我想要编写的一些简单的伪代码:
function gcd(a, b) {
while (b) {
t = b;
b = a mod b;
a = t;
}
return a;
}
我该如何在 TikZ 中编写此类代码来计算 gcd,并且能够编写诸如4 + gcd(15,6)
进一步的 TikZ 表达式之类的东西?请注意,我不是在问如何编写一些标记扩展魔法来计算两个数字的 gcd;我在问是否有可能真正写像这样的简单函数(使用简单的 if、while 和赋值)。
现在我正处于对一切事物都充满期待的状态,您如何处理变量?在 TikZ 示例中,我看到将值简单地存储在宏中的做法很常见,而 TikZ 表达式似乎很乐意与这些宏一起使用。但是,我还必须编写自己的命令来评估 TikZexpr
并将结果存储在我选择的宏中(\pgfmathsetmacro
没有按预期工作,例如\pgfmathsetmacro{\a}{int(0)}\a
产生0.0
而不是0
)。
此外,通过使用普通宏名称,例如\v
,我总是担心我可能会意外地重新定义一些重要的预先存在的宏;但我不想调用我的所有变量\mya
,,\myb
。\myc
有时我很想改变的 cat 代码,以便,:
成为:a
宏名称……但经验告诉我,每次有人弄乱 cat 代码时,一切都会崩溃。:b
:c
答案1
感谢大家的评论,也感谢 Altermundus 的替代答案。然而,我确实认为,对于想要享受更高层次的抽象(而不是低级宏黑客)的用户来说,定义这种简单函数的更好的界面要好得多可以实施。我们真的必须深入到低级 TeX 编码。
暂时我会忘记变量命名空间的问题,但是在您的帮助下,通过对宏进行一些修改、阅读 TikZ 源代码以及经过大量的反复试验,我已经能够提供更接近我想要的东西。
\documentclass{article}
\usepackage{tikz}
\usepackage{etoolbox} % for \ifdefstring
\makeatletter
\newcommand{\setmath}[2]{\pgfmathparse{#2}\edef#1{\pgfmathresult}}
\newcommand{\domath}[1]{\pgfmathparse{#1}\pgfmathresult}
\newcommand{\ifmath}[1]{\pgfmathparse{equal(#1,0)}\ifdefstring\pgfmathresult{0}}
\newcommand{\whilemath}[2]{\ifmath{#1}{#2\whilemath{#1}{#2}}{}}
\newcommand{\returnmath}[1]{\pgfmathparse{#1}\pgfmath@smuggleone\pgfmathresult}
\newcommand{\newmathfunction}[3]{\pgfmathdeclarefunction{#1}{#2}{\begingroup#3\endgroup}}
\makeatother
\newmathfunction{mygcd}{2}{%
\setmath\a{#1}%
\setmath\b{#2}%
\whilemath{\b}{%
\setmath\t{\b}%
\setmath\b{mod(\a,\b)}%
\setmath\a{\t}%
}%
\returnmath{int(\a)}%
}
\begin{document}
\setmath\a{15}
\setmath\b{6}
gcd(\a,\b) = \domath{mygcd(\a,\b)}.
\end{document}
现在你可以读计算 gcd 的代码,基本上是从我原来的伪代码逐行翻译过来的。我知道这可能非常低效,但不管怎样,我只想计算几个点的坐标,我并不是想在这里进行复杂的物理模拟。
我将非常感谢对我所提议的编程接口的任何意见和建议;特别是etoolbox
仅使用与令牌进行比较0
似乎有点小题大做,但这etoolbox
也是我可以轻松学到的东西。
答案2
我认为你收到的请求很糟糕。我会尝试用我糟糕的英语正确地解释我的观点。
你的问题的答案是 lua 和 luatex。如果你想在 TeX 中编程,我认为这就是答案。
pgf/tikz 与 TeX 的集成度非常好,因为 pgf 是用 TeX 构建的。但问题是,pgf 的功能不够强大,而且很难创建复杂的工具,因为这些工具非常慢,有时还不够精确。
如果我们看一下你的例子gcd
,我是\pgfmathgcd{x}{y}
(你需要使用 pgf 2.1简历使用它)。现在你可以写 4+gcd(15,4
。
如何用 TeX 编写这个函数?
\pgfmathdeclarefunction{gcd}{2}{%
\begingroup
\pgfmathcontinuelooptrue
\pgfmath@xa=#1pt
\pgfmath@xb=#2pt
\ifdim\pgfmath@xa=0pt
\pgfmathcontinueloopfalse
\pgfmath@xa=\pgfmath@xb
\fi
\ifdim\pgfmath@xb=0pt
\pgfmathcontinueloopfalse
\pgfmath@xb=\pgfmath@xa
\fi
\ifdim\pgfmath@xa<0pt
\pgfmath@xa=-\pgfmath@xa
\fi
\ifdim\pgfmath@xb<0pt
\pgfmath@xb=-\pgfmath@xb
\fi
\loop
\ifpgfmathcontinueloop
\ifdim\pgfmath@xa=\pgfmath@xb
\pgfmathcontinueloopfalse
\else
\ifdim\pgfmath@xa>\pgfmath@xb
\advance\pgfmath@xa by-\pgfmath@xb
\else
\advance\pgfmath@xb by-\pgfmath@xa
\fi
\fi
\repeat
\pgfmathparse{int(\pgfmath@xa)}%
\pgfmath@smuggleone\pgfmathresult
\endgroup}
好吧,这并不好笑,但你需要用 tex 编程,而不是 latex 或其他东西,而是 tex !! 并且你需要创建一个与其他 pgfmath 函数兼容的函数。if
和while
在这里\ifdim
和 loop
。这是基本的,但就像 Knuth 写的:TeX 并不完全是一种像 C 或 Pascal 这样的编程语言。
实际上,如果你查看 cvs 版本,你会看到一个新的库luamath
。我认为使用 luamath 和 lua,在 TikZ 内部编写数学程序会很有趣且高效。
在现在的 cvs 版本中,您有、和gcd(x,y)
。isodd(x)
我创建这些函数是因为我的包需要它们。iseven(x)
isprime(x)
tkz-berge
最后一点,pgf 是可移植的,如果您创建新的编程工具,则您的工具需要兼容。
我忘记了一些技术性的评论:
1)就像杰克写的\pgfmathtruncatemacro
2) 如果您想使用 \x 或 \a,在 tex 组内写入是可能的。如果您查看 pgfornament.sty,您将看到如下代码:
\def\pgf@@ornament#1{%
\begingroup
\def\i{\pgfusepath{clip}}%
\def\k{\pgfusepath{stroke}}%
\let\o\pgfpathclose
\let\s\pgfusepathqfillstroke
\def\p ##1##2{\pgfqpoint{##1bp}{##2bp}}%
\def\m ##1 ##2 {\pgfpathmoveto{\p{##1}{##2}}}%
\def\l ##1 ##2 {\pgfpathlineto{\p{##1}{##2}}}%
\def\r ##1 ##2 ##3 ##4 {\pgfpathrectangle{\p{##1}{##2}}{\p{##3}{##4}}}%
\def\c ##1 ##2 ##3 ##4 ##5 ##6 {%
\pgfpathcurveto{\p{##1}{##2}}{\p{##3}{##4}}{\p{##5}{##6}}}%
\@@input \OrnamentsFamily#1.pgf%
\endgroup}%