在函数式语言中,通常有一个名为的函数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
很高兴看到expl3
egreg 的答案中使用它。鉴于 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
:我假设当较短的列表结束时映射就停止了。