使用foreach循环实现TiKZ绘图中的重复操作 - registor

发布于 2021-03-26 15:44:48

在使用 TiKZ 绘图时,往往需要大量的重复操作,此时,使用 foreach 循环操作会简化绘制代码,提高绘图代码的编写效率,并方便后期的代码维护工作。

在此,以一个 C 语言中常见的用指针数组实现字符串排序的示意图绘制为例,说明 foreach 循环及 foreach 循环嵌套的操作。

设有如下待排序的字符型指针数组及其初始化值:

char *planets[] = {"Mercury", "Venus", "Earth", "Mars", "Jupiter",
                   "Saturn", "Uranus", "Neptune", "Pluto"};

其中,每一个指针指向的是一个字符型数组,并且根据 C 语言的规定,在每个字符串最后要用\'\0\'表示字符串结束。在 LaTeX 中,可以用 TiKZ 绘制一指向关系,如:

\documentclass[tikz,margin=5pt]{standalone}

% 支持中文
\usepackage{ctex}
% TikZ宏包扩展
\usetikzlibrary{positioning}
\usetikzlibrary{arrows.meta}

% 语法高亮显示排版代码宏包
\usepackage{minted}

% 自定义代码排版命令与环境
% =============================================================================
\usemintedstyle{default}  %codeblocks模式
% 共用设置
\setminted{fontsize=\tiny, breaklines=true, breakautoindent=false}
% 不同字号的行内代码命令
\newmintinline[cinttscr]{c}{fontsize=\scriptsize, escapeinside=||, autogobble}
\newmintinline[cintttny]{c}{fontsize=\tiny, escapeinside=||, autogobble}
% 代码排版环境
\newminted{c}{frame=lines, autogobble}
% =============================================================================

\begin{document} %在document环境中撰写文档
\begin{tikzpicture}[font=\scriptsize,
  x=4.5mm, y=5.0mm,
  cell/.style={draw,
    minimum size=4.5mm,
    inner sep=0pt,
  },
  cellp/.style={draw,
    minimum size=5.0mm,
    inner sep=0pt,
  },
  >=stealth,
  ]

  % 定义数组
  \def\planets{{M,e,r,c,u,r,y}, {V,e,n,u,s}, {E,a,r,t,h}, {M,a,r,s},
    {J,u,p,i,t,e,r}, {S,a,t,u,r,n}, {U,r,a,n,u,s}, {N,e,p,t,u,n,e},
    {P,l,u,t,o}}%
    
  \foreach \l [count=\i] in \planets % \l宏分别取值:{M,e,r,c,u,r,y}、{V,e,n,u,s}等
  {
    \pgfmathsetmacro{\idx}{int(\i-1)} % 计数是从1开始的,减1变为从0开始
    \node[cellp,label=left:\scriptsize \cinttscr{planets[}\idx \cinttscr{]}] (p\i) at (0, 1-\i) {};% 绘制竖向指针数组

    \edef\j{1} % 记录最后一次的\cnt
    \foreach \m [count=\cnt] in \l % \m是{M,e,r,c,u,r,y}中的每一个字母 cnt是计数值
    {
      \node[cell](str\i\cnt) at (\cnt + 1, -\i + 1) {\centering \m};% 绘制横向字符数组
      \xdef\j{\cnt} % 将计数值赋给\j
    }
    \pgfmathsetmacro{\inull}{int(\j+1)} % 计算空字符框编号
    \node[cell, right=-0.11mm of str\i\j](str\i\inull) {\centering \cintttny{\'\0\'}};% 绘制结束空字符
    \draw[fill] (p\i.center) circle (0.05); % 绘制指针数组元素框中心圆点
    \draw[-{Stealth[scale=1.0]}, magenta, thick] (p\i.center) -- (str\i1);% 绘制指针数组元素框中心到字符数组的指向线
  }
      
  % 绘制代码
  \node[scale = 1.0, align = left, text width=0.60\textwidth, above= 0.5 of str12](code1) 
  {
    \begin{ccode}
      char *planets[] = {"Mercury", "Venus", "Earth", "Mars", "Jupiter", 
                          "Saturn", "Uranus", "Neptune", "Pluto"};
    \end{ccode}
  };
\end{tikzpicture}  
\end{document}

其中,最为关键的是使用双重循环分别读取了各字符串及字符串中的各个字符。并使用\pgfmathsetmacro命令对计数值进行了计算和调整,实现了自动化操作。

其绘制结果为:
image.png

另,这里使用了minted宏包实现 C 语言代码的语法高亮显示排版。关于该宏包的使用方法,请在命令行使用texdoc minted查看其使用说明书。

在排序后,各指针的指向会产生改变,此时,可以再使用一次\foreach,根据需要进行对应连线绘制,其核心代码为:

\begin{tikzpicture}[font=\scriptsize,
  x=4.5mm, y=5.0mm,
  cell/.style={draw,
    minimum size=4.5mm,
    inner sep=0pt,
  },
  cellp/.style={draw,
    minimum size=5.0mm,
    inner sep=0pt,
  },
  >=stealth,
  ]

  % 定义数组
  \def\planets{{M,e,r,c,u,r,y}, {V,e,n,u,s}, {E,a,r,t,h}, {M,a,r,s},
    {J,u,p,i,t,e,r}, {S,a,t,u,r,n}, {U,r,a,n,u,s}, {N,e,p,t,u,n,e},
    {P,l,u,t,o}}%

  \foreach \l [count=\i] in \planets %\l 分别是:{M,e,r,c,u,r,y}、{V,e,n,u,s}等
  {
    \pgfmathsetmacro{\idx}{int(\i-1)} % 计数是从1开始的,减1变为从0开始
    \node[cellp,label=left:\scriptsize \cinttscr{planets[}\idx \cinttscr{]}] (p\i) at (0, 1-\i) {};% 绘制竖向指针数组

    \edef\j{1} % 记录最后一次的cnt
    \foreach \m [count=\cnt] in \l % \m是{M,e,r,c,u,r,y}中的每一个字母, cnt是计数值
    {
      \node[cell](str\i\cnt) at (\cnt + 2, -\i + 1) {\centering \m};% 绘制横向字符数组
      \xdef\j{\cnt} % 将计数值赋给\j
    }
        
    \pgfmathsetmacro{\inull}{int(\j+2)} % 为最后一个空字符框编号
    \node[cell, right=-0.11mm of str\i\j](str\i\inull) {\centering \cintttny{\'\0\'}};% 绘制结束空字符        
  }

  % 连线起止编号
  \foreach \i/\j in {1/3, 2/5, 3/4, 4/1, 5/8, 6/9, 7/6, 8/7, 9/2}
  {
    \draw[fill] (p\i.center) circle (0.05); % 绘制指针数组元素框中心圆点
    \draw[-{Stealth[scale=1.0]}, magenta, thick] (p\i.center) -- (str\j1.west);% 绘制指针数组元素框中心到字符数组的指向线
  }
      
  % 绘制代码
  \node[scale = 1.0, align = left, text width=0.60\textwidth, above= 0.5 of str12](code1) 
  {
    \begin{ccode}
      char *planets[] = {"Mercury", "Venus", "Earth", "Mars", "Jupiter", 
                          "Saturn", "Uranus", "Neptune", "Pluto"};
    \end{ccode}
  };
\end{tikzpicture}

其绘制结果为:
image.png

以上LaTeX的代码中,已添加了相关注释说明,在此不再赘述。该代码一定还有不完善之处,敬请大家批评指正。

Happy LaTeXing!

0 条评论

发布
问题