第一个答案

第一个答案

我正在使用refcount包裹获取与标签关联的参考编号。我想确定两个参考是否“相邻”,也就是说:我想要一个可扩展命令形式:

\comparerefs{refOne}{refTwo}

0如果refOne等于refTwo,则会产生1refOne就在之前 refTwo2如果refOne之后立马 refTwo,以及3其他任何情况。

仅仅调用\getrefnumber(从refcount包中),将结果保存到计数器中并加/减 1 不起作用,原因有二:

  1. 如果refOne指的是某个部分(并且我们正在使用该类book),则\getrefnumber返回1.1并设置计数器中断。
  2. 即使只获取章节编号,也必须小心,因为第 1 章第 1 节是不是紧接在第 2 章第 2 节之前。

这显然应该与“更深层次”的引用(即对小节等的引用)一起使用。

需要澄清的是:假设refOne形式为a.b.c,且refTwo形式为d.e.f,则如果= 、=和= ,\comparerefs{refOne}{refTwo}则应返回;如果= 、=和= ,则应返回;如果= 、=和= ,则应返回;如果!= 、或!= 、或!= 、,或者和的长度不同,则应返回。0adbecf\comparerefs{refOne}{refTwo}1adbecf - 1\comparerefs{refOne}{refTwo}2adbecf + 1\comparerefs{refOne}{refTwo}3adbecf - 1ff + 1refOnerefTwo

有想法吗?

答案1

第一个答案

\documentclass[a4paper]{book}
\usepackage{xparse,refcount,etoolbox}
\newcounter{comparerefs}
\newcommand{\comparerefs}[2]{%
  \begingroup\edef\x{\endgroup
    \noexpand\docomparerefs{\getrefnumber{#1}}{\getrefnumber{#2}}}\x}
\NewDocumentCommand{\docomparerefs}{ >{\SplitArgument{2}{.}}m >{\SplitArgument{2}{.}}m }{
  \xcomparerefs#1#2}

\newcommand\xcomparerefs[6]{%
  \ifboolexpr{ test {\ifstrequal{#1}{0}} or test {\ifstrequal{#4}{0}} }% reference is undefined
    {\setcounter{comparerefs}{-1}}
    {\ycomparerefs{#1}{#2}{#3}{#4}{#5}{#6}}%
}
\newcommand{\ycomparerefs}[6]{%
  \ifboolexpr{ test {\ifstrequal{#3}{\NoValue}} and test {\ifstrequal{#6}{\NoValue}} }
    {\zcomparerefs{#1}{#2}{#4}{#5}}
    {\subseccomparerefs{#1}{#2}{#3}{#4}{#5}{#6}}%
}
\newcommand{\zcomparerefs}[4]{%
  \ifboolexpr{ test {\ifstrequal{#2}{\NoValue}} and test {\ifstrequal{#4}{\NoValue}} }
    {\chapcomparerefs{#1}{#3}}
    {\seccomparerefs{#1}{#2}{#3}{#4}}%
}
\newcommand{\chapcomparerefs}[2]{%
  \ifnum#1=#2\relax
    \setcounter{comparerefs}{0}%
  \else
    \ifnum#1=\numexpr#2-1\relax\relax
      \setcounter{comparerefs}{1}%
    \else
      \ifnum#1=\numexpr#2+1\relax\relax
        \setcounter{comparerefs}{2}%
      \else
        \setcounter{comparerefs}{3}%
      \fi
    \fi
  \fi
}
\newcommand{\seccomparerefs}[4]{%
  \ifnum#1=#3\relax
    \chapcomparerefs{#2}{#4}%
  \else
    \setcounter{comparerefs}{3}%
  \fi}
\newcommand{\subseccomparerefs}[6]{%
  \ifnum#1=#4\relax
    \seccomparerefs{#2}{#3}{#5}{#6}%
  \else
    \setcounter{comparerefs}{3}%
  \fi
}


\begin{document}
\chapter{Abc}\label{x}
\section{Abc}
\subsection{Def}\label{A}
\subsection{Ghi}\label{B}

\section{XXX}
\subsection{UUU}\label{C}

\chapter{YYY}\label{y}

\chapter{ZZZ}\label{z}

%\comparerefs{x}{x}\showthe\value{comparerefs}
%\comparerefs{x}{y}\showthe\value{comparerefs}
%\comparerefs{y}{x}\showthe\value{comparerefs}
%\comparerefs{x}{z}\showthe\value{comparerefs}

\comparerefs{A}{A}\showthe\value{comparerefs}
\comparerefs{A}{B}\showthe\value{comparerefs}
\comparerefs{B}{A}\showthe\value{comparerefs}
\comparerefs{A}{C}\showthe\value{comparerefs}


\end{document}

我使用的\SplitArgument功能解析获取已拆分成组件的引用。我只支持最多三个级别(子部分),并且没有检查引用是否不兼容。

如果其中一个引用尚未解析,则计数器将设置为 -1。

首先,我们决定引用的是章节、节还是小节。因此,\chapcomparerefs是主要的,并将计数器设置为请求的值。如果是,\seccomparerefs我们检查第一个组件是否相等,然后将控制权传递给\chapcomparerefs第二个组件,否则我们将计数器设置为 3。同样,\subseccomparerefs如果第一个组件不同,则将计数器设置为 3,否则调用\seccomparerefs

如果您比较不同类型的参考文献,就会出现“缺失数字”错误。

第二个答案

\documentclass[a4paper]{book}
\usepackage{xparse,refcount,etoolbox}
\newcounter{comparerefs}
\newcommand{\comparerefs}[2]{%
  \begingroup\edef\x{\endgroup
    \noexpand\docomparerefs{\getrefnumber{#1}}{\getrefnumber{#2}}}\x}
\NewDocumentCommand{\docomparerefs}{ >{\SplitArgument{20}{.}}m >{\SplitArgument{20}{.}}m }{
  \setcounter{comparerefs}{0}\begingroup
  \def\NoValue{-1000}\edef\x{\endgroup
    \noexpand\xcomparerefs#1\relax#2\relax}\x}

\def\xcomparerefs#1#2#3\relax#4#5#6\relax{%
  \ycomparerefs{#1}{#2}{#4}{#5}{{#2}#3\relax{#5}#6\relax}}
\def\ycomparerefs#1#2#3#4#5{%
  \ifboolexpr{ test {\ifnumcomp{#1}{=}{#3}} and 
     not ( test {\ifnumcomp{#2}{=}{-1000}} and test {\ifnumcomp{#4}{=}{-1000}} ) }
    {\xcomparerefs#5}
    {\decide{#1}{#2}{#3}{#4}}%
}
\def\decide#1#2#3#4{%
  \ifboolexpr{ test {\ifnumcomp{#2}{=}{-1000}} and test {\ifnumcomp{#4}{=}{-1000}} }
     {\ifnumcomp{#1}{=}{#3}{}{\xdecide{#1}{#3}}}
     {\ifnumcomp{#1}{=}{#3}{\xdecide{#2}{#4}}{\setcounter{comparerefs}{3}}}%
}
\def\xdecide#1#2{%
  \ifnumcomp{#1}{=}{#2-1}
    {\setcounter{comparerefs}{1}}
    {\ifnumcomp{#1}{=}{#2+1}
       {\setcounter{comparerefs}{2}}
       {\setcounter{comparerefs}{3}}%
    }
}

\begin{document}
\chapter{Abc}\label{x}
\section{Abc}
\subsection{Def}\label{A}
\subsection{Ghi}\label{B}

\section{XXX}
\subsection{UUU}\label{C}

\chapter{YYY}\label{y}

\chapter{ZZZ}\label{z}

\comparerefs{x}{x}\showthe\value{comparerefs}
\comparerefs{x}{y}\showthe\value{comparerefs}
\comparerefs{y}{x}\showthe\value{comparerefs}
\comparerefs{x}{z}\showthe\value{comparerefs}

\comparerefs{A}{A}\showthe\value{comparerefs}
\comparerefs{A}{B}\showthe\value{comparerefs}
\comparerefs{B}{A}\showthe\value{comparerefs}
\comparerefs{A}{C}\showthe\value{comparerefs}

\comparerefs{A}{x}\showthe\value{comparerefs}
\end{document}

这些宏最多允许 20 个级别(应该足够了)。再次解析用于从引用中获取两个参数列表。我假设没有引用值为 -1000。测试结果存储在计数器中comparerefs

第三个答案

这是第二个答案的可扩展版本(将\edef证明这一断言)。作为副产品,字符串的长度是无限的,只要它们都是规定的形式,即用句点分隔的数字。所有数字都不应为 -1000,这是用于终止递归的特殊值。

\documentclass[a4paper]{book}
\usepackage{refcount,etoolbox}
\newcommand{\comparerefs}[2]{%
  \expandafter\comparerefsA\expandafter{\romannumeral-`a\getrefnumber{#2}}{#1}}
\def\comparerefsA#1#2{%
  \expandafter\comparerefsB\expandafter{\romannumeral-`a\getrefnumber{#2}}{#1}}
\def\comparerefsB#1#2{%
  \xcomparerefs#1.-1000\relax#2.-1000\relax}

\def\xcomparerefs#1.#2.#3\relax#4.#5.#6\relax{%
  \ycomparerefs{#1}{#2}{#4}{#5}{#2.#3.-1000.-1000\relax#5.#6.-1000.-1000\relax}}
\def\ycomparerefs#1#2#3#4#5{%
  \ifboolexpe{ test {\ifnumcomp{#1}{=}{#3}} and 
     not ( test {\ifnumcomp{#2}{=}{-1000}} and test {\ifnumcomp{#4}{=}{-1000}} ) }
    {\xcomparerefs#5}
    {\decide{#1}{#2}{#3}{#4}}%
}
\def\decide#1#2#3#4{%
  \ifboolexpe{ test {\ifnumcomp{#2}{=}{-1000}} and test {\ifnumcomp{#4}{=}{-1000}} }
     {\ifnumcomp{#1}{=}{#3}{0}{\xdecide{#1}{#3}}}
     {\ifnumcomp{#1}{=}{#3}{\xdecide{#2}{#4}}{3}}%
}
\def\xdecide#1#2{%
  \ifnumcomp{#1}{=}{#2-1}
    {1}
    {\ifnumcomp{#1}{=}{#2+1}
       {2}
       {3}%
    }
}

\begin{document}
\chapter{Abc}\label{x}
\section{Abc}
\subsection{Def}\label{A}
\subsection{Ghi}\label{B}

\section{XXX}
\subsection{UUU}\label{C}

\chapter{YYY}\label{y}

\chapter{ZZZ}\label{z}

\comparerefs{x}{y}\par
\comparerefs{y}{x}\par
\comparerefs{x}{z}\par

\comparerefs{A}{A}\par
\comparerefs{A}{B}\par
\comparerefs{B}{A}\par
\comparerefs{A}{C}\par

\comparerefs{A}{x}\par
\end{document}

对“可扩展”解决方案的评论

\comparerefs读取其参数,并首先尝试\getrefnumber从中获取扩展。我使用了\romannumeral-`a两次技巧:\expandafter将达到\romannumeral,因此从\comparerefs{X}{Y}我们得到

\comparerefsA{<expansion of \getrefnumber{Y}}{X}

将扩大到

\expandafter\comparerefsB\expandafter
  {\romannumeral-`a\getrefnumber{X}}
  {<expansion of \getrefnumber{Y}}

将成为

\comparerefsB{<expansion of \getrefnumber{X}}{<expansion of \getrefnumber{Y}}

假设\getrefnumber{X}扩展为1.1.1\getrefnumber{Y}扩展为1.2(不同长度);在这种情况下,连续扩展将是

\xcomparerefs 1.1.1.-1000.-1000\relax 1.2.-1000.-1000\relax

\xcomparerefs只需选择要比较的数字,我们就会得到

\ycomparerefs{1}{1}{1}{2}{1.1.-1000\relax 2.-1000\relax}

第五个参数将在启动递归的情况下使用,并再次输入\xcomparerefs(我们-1000在末尾添加了一个以确保数字永远不会用完,因为两个序列的长度可以不同)。

#1=#3如果和都不同于 -1000,则开始递归:在这种情况下,我们无法确定字典顺序,因此我们砍掉第一个组件并重新#2开始。#4

当上述条件不满足时,将发生最后阶段。如果和#2都是#4-1000,则如果#1#2相等(两个原始序列确实相等),我们输出 0。否则,我们通过比较#1和来选择要输出的数字#3

如果#2#4不都是 -1000,我们再次比较#1#3:如果它们不同,则这两个序列“彼此相距甚远”,我们输出 3;否则我们通过比较#2和来选择输出#4

决策宏查看这两个数字,如果第一个数字比第二个数字小一,则输出 1;如果第一个数字比第二个数字多一,则输出 2;其他所有情况下,则输出 3。

\ifboolexpe由于宏提供,所有内容均可扩展电子工具箱

答案2

这是我的必pgfkeys答。它能满足您的要求,但缺少可扩展性:它接受任意长度(可能不相等)的引用字符串,并根据它们是否相等、在最后一个组件中相邻或其他情况返回 0、1、2 或 3。我确实假设它们的数字高于 -1000:您没有任何非常负数的部分,对吗?

工作原理:高级

  • 代码分为两部分:首先,我将字符串(例如1.2.34.5.6)转换为我更喜欢的形式。我很早就决定使用来\foreach进行循环,它允许“并行处理”形式\x/\y in {1/4, 2/5, 3/6},因此我需要沿句点拆分引用,然后通过匹配相应的部分(用斜线分隔)重新组合它们。

  • 此操作还使我有机会修复不等长引用:我决定,给定1.2.31.1,对将是1/1, 2/1, 3/-1000。这就是为什么我假设部分相当大。

  • 代码的第二部分是比较。它的工作原理正如您所想的那样:给定一个形式为 的 foreach 循环\x/\y = {1/1, etc.},我只需依次检查如何\x\y进行比较并相应地设置结果。我还检查我之前是否已经确定引用是不等:在这种情况下,结果始终为 3,因为这意味着它们在结尾之外的某个地方有所不同。如果其中一个比另一个短,则始终会触发此操作。

现在你可以试着跟着代码走。如果你遇到困难,后面还有更多解释:

\documentclass{article}
\usepackage{pgfkeys,pgffor,etoolbox}

\newcommand\comparerefs[2]{%
 \pgfkeys{
  /split and combine,
  reset,
  do = {#1.\Stop.}{#2.\Stop.},
 }%
 \pgfkeys{
  /lexicographic,
  compare adjacent/.expanded = {\pgfkeysvalueof{/split and combine/list of pairs}},
 }%
}

% To protect against \edef
\def\Stop{\noexpand\Stop}

\pgfkeys{
 /split and combine/.is family, /split and combine,
 do/.code 2 args = {%
  \GetPeriod{\beforeone}{\afterone}#1%
  \GetPeriod{\beforetwo}{\aftertwo}#2%
  \ifdefempty{\beforeone}
   {%
    \ifdefempty{\beforetwo}
     {\pgfkeysalso{next/.style = {}}}
     {\pgfkeysalso{list of pairs/.append/.expanded = {,-1000/\beforetwo}}}%
   }
   {%
    \ifdefempty{\beforetwo}
     {\pgfkeysalso{list of pairs/.append/.expanded = {,\beforeone/-1000}}}
     {\pgfkeysalso{list of pairs/.append/.expanded = {,\beforeone/\beforetwo}}}%
   }%   
  \pgfkeysalso{next}%
 },
 reset/.style = {
  list of pairs/.initial = {0/0},
  next/.style = loop,
 },
 loop/.style = {do/.expanded = {\afterone}{\aftertwo},},
}

% Low-level stuff
\makeatletter
\def\GetPeriod#1#2#3.{%
 \ifx\Stop#3%
  \expandafter\@firstoftwo
 \else
  \expandafter\@secondoftwo
 \fi
 {\def#1{}\def#2{\Stop.}}
 {\def#1{#3}\GetToStop#2}%
}
\def\GetToStop#1#2\Stop.{%
 \def#1{#2\Stop.}%
}
\makeatother

\pgfkeys{
 /handlers/.list pairs/.code = {%
  \edef\thiskey{\pgfkeyscurrentpath}%
  \foreach \x/\y in {#1} {%
   \globaldefs=1%
   \pgfkeysalso{\thiskey/.expanded = {\x}{\y}}%
   \globaldefs=0%
  }%
 }
}

\pgfkeys{
 /lexicographic/.is family, /lexicographic,
 compare adjacent/.style = {
  result/.initial = 0,
  compare one/.list pairs = {#1},
  result
 },
 compare one/.code 2 args = {%
  \ifnumequal{\pgfkeysvalueof{/lexicographic/result}}{0}
   {%
    \ifnumequal{#1}{#2}
     {\pgfkeysalso{result = 0}}
     {%
      \ifnumequal{#1 + 1}{#2}
       {\pgfkeysalso{result = 1}}
       {%
        \ifnumequal{#1}{#2 + 1}
         {\pgfkeysalso{result = 2}}
         {\pgfkeysalso{result = 3}}
       }%
     }%
   }%
   {%
    \pgfkeysalso{result = 3}%
    \breakforeach
   }%
 },
}

\begin{document}
 \setlength\parindent{0pt}
 \comparerefs{1}{1}

 \comparerefs{1}{2}

 \comparerefs{2}{1}

 \comparerefs{3}{1}

 \comparerefs{1.2}{1.2}

 \comparerefs{1.2}{1.3}

 \comparerefs{1.3}{1.2}

 \comparerefs{1.3}{1.1}

 \comparerefs{1.2.3}{2.2.3}

 \comparerefs{1.2.3}{1.3.3}

 \comparerefs{1.2.3}{1.1}

\end{document}

工作原理:低级

我的风格pgfkeys是将所有内容放入适当命名的系列中,因此我们有两个系列:/split and combine第一阶段和/lexicographic第二阶段(我猜这是一种字典顺序)。

工作原理如下/split and combine

  • 它是递归的:只有一个键,do它获取每个引用的第一个组件,将它们连接起来(-1根据需要插入),然后如果任一引用仍有更多部分,则调用自身。

  • pgfkeys获取操作由非宏完成\GetPeriod,它使用分隔参数进行一些模式匹配\def。它的工作原理与您预期的一样,只是如果它碰巧发现引用超出部分,它不会将该after部分清空:它会重新放入终止标志并返回它。这样,递归就可以顺利地继续,直到两个都 before部分是空的。

  • 代码中还有我最喜欢的技巧:\Stop是递归的,但同时也防止扩展!这样,如果\Stop出现在 中\edef,它会扩展为\noexpand\Stop,然后扩展为\Stop,最后停止。这样,我就可以完全扩展after递归时的各个部分,而不必担心无限循环或者关于\Stop下次重新插入。尽管我一直在推动将其pgfkeys作为 TeX 编程的更好替代品,但这就是让 TeX 变得有趣的事情。

工作原理如下/lexicographic

  • compare adjacent针对每对单独的参考号重复调用compare one。就是这样。

  • 我确实必须去添加一个处理程序.list pairs,因为pgfkeys默认情况下没有“斜线”语法的接口,这给我带来了很多麻烦,因为\foreach它将其主体埋在组内,因此所有内容都是本地的。result但是,我需要一劳永逸地设置,因此必须覆盖它;解决方案,正如我从答案中学到的有没有办法使用“pgfkeys”设置*全局*键值?,就是设置\globaldefs=1。我尽量不要做得过分;毫无疑问,我已经像这样破坏了保存堆栈。

我对过度使用条件语句有些不满。我真的很想使用pgfkeys' 键测试逻辑 ( .try/ .retry) 来实现if/then/else语句,但效果并不好。当然,可以定义一个非常通用的 " if, then, else" 键来执行此操作,但我太懒了,只是把它变成了代码。不过,我仍然从中受益匪浅pgfkeys,因为它的.expanded处理程序简单,语法也很好。只是没有我想要的那么多。

相关内容