制作 zip 宏

制作 zip 宏

在函数式语言中,通常有一个名为的函数zip,它在很多情况下都很方便;例如在 ruby​​ 中,我们可以这样说:

%w(a b c).zip(%w(d e f))

获取数组的数组[["a", "d"], ["b", "e"], ["c", "f"]]

似乎有很多方法可以创建一个zip函数,要么用map另一个), 或者foldr。(map实际上可能已经完成了foldr。)

但鉴于lambda.sty列表似乎不支持嵌套列表,我不确定如何zip在 TeX 中制作(使用lambda.sty)。

我尝试了一些看似最简单的方法;

\catcode`@=11
\input lambda.sty

\def\Zipwith#1#2#3{\Foldr#1{#3}{#2}}

\def\lista{\Listize[a,b,c]}
\def\listb{\Listize[d,e,f]}

\Unlistize{\Zipwith\Map\lista\listb}
\bye

但结果是[abcd, abce, abcf]。所以,差一点就成功了。

您有什么想法吗?

因此,我实际上并不需要嵌套列表,只是对于lista压缩的listb,一些宏\foo#1#2会运行(继续使用示例列表)三次,在第一次运行#1<-a, #2<-d、第二次运行#1<-b, #2<-e和第三次运行中作为其参数#1<-c, #2<-f

答案1

很高兴看到expl3egreg 的答案中使用它。鉴于 LaTeX3 界面的最新发展,我想“稍微”改进或更改它,如下所示:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

% package level definitions

\seq_new:N \l__morbusg_left_seq
\seq_new:N \l__morbusg_right_seq

% public package interface command

\cs_new:Npn \morbusg_zip:Nnnnn #1 #2 #3 #4 #5 {
  \seq_set_split:Nnn \l__morbusg_left_seq   { #2 } { #3 }
  \seq_set_split:Nnn \l__morbusg_right_seq { #4 } { #5 }
  \seq_mapthread_function:NNN \l__morbusg_left_seq \l__morbusg_right_seq #1
}

% xparse interface

\NewDocumentCommand{\zip}{ m O{,} m o m }
 { \IfNoValueTF {#4}
      { \morbusg_zip:Nnnnn #1 {#2} {#3} {#2} {#5} }
      { \morbusg_zip:Nnnnn #1 {#2} {#3} {#4} {#5} }
 }

\ExplSyntaxOff

\newcommand{\foo}[2]{First is #1, second is #2\par}
\newcommand{\baz}[2]{Left is #1, right is #2\par}

\begin{document}
\zip{\foo}{a,b,c}{d,e,f}

\zip{\baz}[;]{A;B;C}{D;E;F}

\zip{\baz}{1,2,3}[!!]{X!!Y!Y!!Z}

\zip{\foo}[&]{9&8&7}[$]{5$4$3}
\end{document}

那么有什么不同呢?

\morbusg_zip:Nnnnn功能界面与文档级界面清晰分离\zip

正如我在我可以做什么来帮助 LaTeX3 项目LaTeX3 的架构将两者非常清晰地区分开来。xparse是一个文档级语法包,它使用可选参数等实现类似 LaTeX2e 的语法。但可能会有替代方案,例如,键/值类型的文档级语法,或类似 xml 的语法。

如果正确分离功能代码,即,\morbusg_zip:Nnnnn将保持不变,您只需切换文档级解析例程。在这个特定示例中,可能感觉有点矫枉过正,但始终以这种方式思考并将分离作为最佳实践会有所帮助。

支持两个列表上的不同分隔符。

原始解决方案使用同一个运算符。我扩展了函数定义,使每个列表使用一个分隔符。因此,函数定义提供了更多可能性,是否使用它们取决于用户界面,例如,要获取 egreg 的接口,其定义应该\zip

\NewDocumentCommand{\zip}{ m O{,} m m }
    { \morbusg_zip:Nnnnn #1 {#2} {#3} {#2} {#4} }

即对两个列表重复使用相同的分隔符。这再次表明 (imnsho) 分离两个级别的优点。

明确区分公共接口和内部命令和变量

嗯,这次我们只有一个函数,而且它是公共的,所以只有两个变量被标记为内部变量(它们__在名称中使用)。在包中,可以将其缩短为,\l_@@_left_seq其中是当前“模块”名称的简写,前面@@有两个。__

如果扩展版本通过 TeX 运行,您将获得以下输出(如您所见,奇怪的分隔符也是可能的,例如,!!或,$但这已经在原始解决方案中了):

在此处输入图片描述

答案2

像这样吗?

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\seq_new:N \l_morbusg_left_seq
\seq_new:N \l_morbusg_right_seq

\NewDocumentCommand{\zip}{ m O{,} m m }
 {
  \seq_set_split:Nnn \l_morbusg_left_seq  { #2 } { #3 }
  \seq_set_split:Nnn \l_morbusg_right_seq { #2 } { #4 }
  \seq_mapthread_function:NNN \l_morbusg_left_seq \l_morbusg_right_seq #1
 }
\ExplSyntaxOff

\newcommand{\foo}[2]{First is #1, second is #2\par}

\newcommand{\baz}[2]{Left is #1, right is #2\par}

\begin{document}
\zip{\foo}{a,b,c}{d,e,f}

\zip{\baz}[;]{A;B;C}{D;E;F}
\end{document}

第一个参数\zip是应用的两个参数宏;可选参数是列表分隔符;然后是两个列表。

在此处输入图片描述

答案3

LaTeX3 解决方案可能是我解决这个问题的方法(序列数据结构设计得非常好),但如果我们假设列表使用逗号分隔符,如问题所示,那么

\catcode`\@=11 %
\long\def\zip#1#2#3{%
  \zip@#3,#1,\q@recursion@tail,\q@mark,#2,\q@recursion@tail,\q@recursion@stop
}
\long\def\zip@#1,#2,#3\q@mark,#4,{%
  \stop@if@quark@recursion@tail{#2}
  \stop@if@quark@recursion@tail{#4}
  #1{#2}{#4}%
  \zip@#1,#3\q@mark,%
}
\long\def\stop@if@quark@recursion@tail#1{%
  \ifnum\pdfstrcmp{\unexpanded{#1}}{\noexpand\q@recursion@tail}=\z@
    \expandafter\stop@if@quark@recursion@tail@aux
  \fi
}
\long\def\stop@if@quark@recursion@tail@aux#1\q@recursion@stop{}
\catcode`\@=12 %
\def\foo#1#2{[#1,#2]}
\zip{a,b,c}{1,2,3}\foo
\csname @@end\endcsname
\end

可以工作。(注意:这需要 pdfTeX,但也可以与 XeTeX 和 LuaTeX 配合使用,但需要正确的设置\pdfstrcmp。没有这个原语的设置也是可行的,但比较麻烦。)

这里的想法是每次从每个列表中抓取一个项目。检查每个项目的结束标记\q@recursion@tail:我假设当较短的列表结束时映射就停止了。

相关内容