J 中的笛卡尔积
Cartesian product in J
我正在尝试为 J 中的生命游戏功能重现 APL 代码。搜索 "Game of Life in APL" 后可以找到解释此代码的 YouTube 视频。目前我有一个矩阵 R
是:
0 0 0 0 0 0 0
0 0 0 1 1 0 0
0 0 1 1 0 0 0
0 0 0 1 0 0 0
0 0 0 0 0 0 0
我编写了生成邻接列表(相邻方块中的活细胞数)的 J 代码,如下所示:
+/ ((#:i.4),-(#:1+i.2),(1 _1),.(_1 1)) |. R
并产生:
0 0 1 2 2 1 0
0 1 3 4 3 1 0
0 1 4 5 3 0 0
0 1 3 2 1 0 0
0 0 1 1 0 0 0
我对这段代码的主要问题是它不够优雅,因为需要 ((#:i.4),-(#:1+i.2),(1 _1),.(_1 1))
才能生成:
0 0
0 1
1 0
1 1
0 _1
_1 0
_1 1
1 _1
这实际上只是向量 1 0 _1
与其自身之间的外积或笛卡尔积。我找不到生成此笛卡尔积的简单方法,所以我的最终问题是如何更优雅地生成所需的向量?
,"0/ ~ 1 0 _1
将为您提供所需的笛卡尔积(但您可能希望将其整形为 9 乘 2)。
笛卡尔积是一元动词catalog
: {
{ ;~(1 0 _1)
┌────┬────┬─────┐
│1 1 │1 0 │1 _1 │
├────┼────┼─────┤
│0 1 │0 0 │0 _1 │
├────┼────┼─────┤
│_1 1│_1 0│_1 _1│
└────┴────┴─────┘
为 9,2 列表整理 (,
) 和拆箱 (>
):
>,{ ;~(1 0 _1)
1 1
1 0
1 _1
0 1
0 0
0 _1
_1 1
_1 0
_1 _1
完整目录
非常简洁明了。 J 习语 table (f"0/~
) 的绝妙例子。我喜欢它,因为它展示了 J 的微妙设计如何让我们概括和扩展三年级的任何人都熟悉的概念:算术 tables(加法 tables,+/~ i. 10
和乘法 tables */~ i.12
¹),即使在 APL 中也相对笨拙。
除了这个很好的答案之外,还值得注意的是 J 中内置了一个原语来计算笛卡尔积,monad {
。
例如:
> { 2 # <1 0 _1 NB. Or i:_1 instead of 1 0 _1
1 1
1 0
1 _1
0 1
0 0
0 _1
_1 1
_1 0
_1 _1
盘点
请注意,monad {
的输入是一个框列表,该列表中框的数量决定了每个组合中元素的数量。包含两个框的列表生成一个二元组数组,包含三个框的列表生成一个三元组数组,依此类推。
有点过分
鉴于完整的外积(笛卡尔积)如此昂贵(O(n^m)
),有人不禁要问:为什么 J 对此有原语?
当我们检查 monad {
的输出时,也会出现类似的疑虑:为什么它是装箱的?当且仅当我们想要合并不兼容类型和形状的数组时,方框才会在 J 中使用。但是根据 {
.
的定义,{ y
的所有结果都将具有相同的类型和形状
那么,是什么原因呢?好吧,事实证明这两个问题是相关的,并且是合理的,一旦我们理解了为什么首先引入 monad {
。
我对此感到矛盾
我们必须记住,J 中的所有动词都必然是矛盾的。 J 的语法不允许只有单子或二元的动词。当然,一个价或另一个价可能有一个空域(即没有有效输入,如 monad E.
或 dyad ~.
或 [:
的任一价),但它仍然 存在。
具有空域的价是 "real",但它的有效输入范围是空的(有效输入范围的想法的扩展 +
是数字,以及任何东西否则,像字符一样,产生 "domain error").
好的,好的,所以所有的动词都有两个价,那又怎样?
精选历史
嗯,肯·艾弗森 (Ken Iverson) 对 J 的主要设计目标之一,在长期使用 APL 后,放弃了数组索引的括号表示法(例如 A[3 4;5 6;2]
),并认识到 select来自数组的离子是一个函数。
这是一个巨大的洞察力,对语言的设计和使用都产生了严重影响,不幸的是我没有 space 进入这里。
并且由于所有函数都需要一个名称,我们必须给 selection 函数起一个名字。 J 中的所有原始动词都拼写为字形、变形字形(在我看来,.
、:
、.:
等后缀是变音符号)或变形字母数字。
现在,因为 selection 所以 是面向数组编程的常见和基础,它被赋予了一些主要的房地产(J 正字法中的区别标志), 单字符字形:{
²。
因此,由于 {
被定义为 selection,而 selection 当然是二元的(即有两个参数:索引和数组),这说明对于二元组 {
。现在我们明白了为什么要注意所有动词都是矛盾的。
我想我正在接受一个主题
在设计语言时,最好给 monad {
一些与 "selection" 的主题关系;出于优雅和助记的目的,将动词的两个价在主题上联系起来是 J 中的常见模式。
这个广泛的模式也是一个值得单独讨论的话题,但现在让我们关注为什么选择目录/笛卡尔积作为 monad {
。有什么关系?是什么导致了另一个怪癖,即它的结果总是被装箱?
托槽切除术
好吧,请记住引入 {
是为了取代——完全取代——APL 和(以及许多其他编程语言和符号)的旧括号下标语法。这立即使 selection 变得更容易、更有用,并且还简化了 J 的语法:在 APL 中,语法和解析器必须具有特殊的索引规则,例如:
A[3 4;5 6;2]
语法异常。但是,天哪,从程序员的角度来看,这不是很有用且富有表现力吗?
但这是为什么呢?是什么导致了多维括号表示法的经济性?这么短的时间怎么能说这么多space?
好吧,让我们看看我们在说什么。在上面的表达式 A[3 4;5 6;2]
中,我们要求第 3rd 和第 4th 行,第 5第 th 和第 6th 列,以及第 2nd 平面。
也就是我们要
- 平面 2,第 3 行,第 5 列,并且
平面 2,第 3 行,第 6 列,并且
平面 2,第 4 行,第 5 列和
- 平面 2,第 4 行,第 6 列
想一想。我会等。
Ken 让你大吃一惊的时刻
轰,对吧?
Indexing is a Cartesian product.
一直都是。但是 Ken 看到了。
所以,现在,在 APL 中我们不说 A[3 4;5 6;2]
(有些人挥手说 []IO
是 1
还是 0
),我们说:
(3 4;5 6;2) { A
当然,这只是 shorthand,或语法糖,用于:
idx =. { 3 4;5 6;2 NB. Monad {
idx { A NB. Dyad {
所以我们保留了熟悉的、方便的、暗示性的分号语法(what do you want to bet being spelled ;
is also not a巧合?)同时获得将 {
变成第一个 class 函数的所有好处,因为它本来应该是³。
打开神秘盒子
这让我们回到另一个最后的狡辩。 如果 monad {
的结果在类型和形状上都是规则的,那为什么还要装箱?那岂不是多余又不方便?
嗯,是的,但请记住,x { y
中的未装箱即数字 LHA 仅 selects 项目 来自 y
。
这很方便,因为经常需要多次 select 相同的项目(例如,将 'abc'
替换为 'ABC'
并将任何非 abc 字符默认为 '?'
,我们通常会说 ('abc' i. y) { 'ABC','?'
,但这只有效,因为我们被允许 select 多次索引 4,即 '?'
。
但是这种便利性排除了使用直接数值数组来也进行多维索引。也就是说,未装箱的数字对 select 项目(最常见的用例)的便利性干扰了 also 使用未装箱的数字数组来表达,例如A[17;3;8]
通过 17 3 8 { A
。我们不能两全其美。
所以我们需要一些其他符号来表达多维 select 离子,并且由于二元体 {
的左秩为 0(正是 因为 前述),并且单个原子盒可以封装任意结构,盒是完美的候选者。
所以,为了表达 A[17;3;8]
而不是 17 3 8 { A
,我们简单地说 (< 17;3;8) { A
,这又是直接、方便和熟悉的,并且允许我们做任意数量的同时存在多维 select 离子,例如( (< 17;3;8) , (<42; 7; 2) { A
),这是您在面向数组的语言中想要和期望的。
这意味着,当然,为了产生 dyad {
期望作为输入的那种输出,monad {
必须产生 boxes ⁴。 QED.
哦,还有 PS:正如我所说,装箱允许单个原子中的 任意 结构,如果我们不装箱会发生什么,甚至是一个盒子列表,但盒子是一个盒子?好吧,你有没有想过用一种方式来表达 "I want every index except the last" 或第 3 位,或第 42 位和第 55 位?嗯...
脚注:
¹ 请注意,在算术 tables +/~ i.10
和 */~ i.12
中,我们可以省略显式 "0
(出现在 ,"0/~ _1 0 1
) 因为算术动词已经是标量的(显然)
² 但是为什么 selection 给出了 那个特定的 字形,{
?
好吧,Ken 故意不透露 J 的正字法中使用的具体助记符选择,因为他不想为他的用户口述这样的个人选择,但对我来说,Dan,{
看起来像一个从右向左指向的小漏斗。也就是右边的数据流很大,左边的数据流小很多,像水龙头滴水一样。
同样,我也经常看到二元体|:
像一点咖啡table或者巨石阵三体巨石被踢翻在一边,即转置.
而monad #
显然是助记符(count, tally, 项目数量),但对偶总是对我很有启发性,因为它看起来像一个小网,保留感兴趣的项目并让其他一切"fall through".
但是,当然,YMMV。这正是 Ken 从未为我们详细说明的原因。
³ 您是否还注意到,在 APL 中,作为控制数据的索引列在数组的右侧,而在 J 中它们现在在左边,控制数据属于哪里?
⁴ 虽然这个 Jer 仍然希望看到 monad {
产生未装箱的结果,但代价是 J 引擎内的一些额外复杂性,即以单个实现者为代价,并使语言的每个用户受益
n 有很多有趣的文献更详细地介绍了这个material,但不幸的是我现在没有时间去把它挖出来。如果有足够的兴趣,我可能会稍后回来编辑参考答案。现在,值得一读 Mastering J, an early paper on J by one of the luminaries of J, Donald McIntyre, which makes mention of the eschewing of the "anomalous bracket notation" of APL, and perhaps a tl;dr version of this answer I personally posted to the J forums in 2014.
我正在尝试为 J 中的生命游戏功能重现 APL 代码。搜索 "Game of Life in APL" 后可以找到解释此代码的 YouTube 视频。目前我有一个矩阵 R
是:
0 0 0 0 0 0 0
0 0 0 1 1 0 0
0 0 1 1 0 0 0
0 0 0 1 0 0 0
0 0 0 0 0 0 0
我编写了生成邻接列表(相邻方块中的活细胞数)的 J 代码,如下所示:
+/ ((#:i.4),-(#:1+i.2),(1 _1),.(_1 1)) |. R
并产生:
0 0 1 2 2 1 0
0 1 3 4 3 1 0
0 1 4 5 3 0 0
0 1 3 2 1 0 0
0 0 1 1 0 0 0
我对这段代码的主要问题是它不够优雅,因为需要 ((#:i.4),-(#:1+i.2),(1 _1),.(_1 1))
才能生成:
0 0
0 1
1 0
1 1
0 _1
_1 0
_1 1
1 _1
这实际上只是向量 1 0 _1
与其自身之间的外积或笛卡尔积。我找不到生成此笛卡尔积的简单方法,所以我的最终问题是如何更优雅地生成所需的向量?
,"0/ ~ 1 0 _1
将为您提供所需的笛卡尔积(但您可能希望将其整形为 9 乘 2)。
笛卡尔积是一元动词catalog
: {
{ ;~(1 0 _1)
┌────┬────┬─────┐
│1 1 │1 0 │1 _1 │
├────┼────┼─────┤
│0 1 │0 0 │0 _1 │
├────┼────┼─────┤
│_1 1│_1 0│_1 _1│
└────┴────┴─────┘
为 9,2 列表整理 (,
) 和拆箱 (>
):
>,{ ;~(1 0 _1)
1 1
1 0
1 _1
0 1
0 0
0 _1
_1 1
_1 0
_1 _1
完整目录
f"0/~
) 的绝妙例子。我喜欢它,因为它展示了 J 的微妙设计如何让我们概括和扩展三年级的任何人都熟悉的概念:算术 tables(加法 tables,+/~ i. 10
和乘法 tables */~ i.12
¹),即使在 APL 中也相对笨拙。
除了这个很好的答案之外,还值得注意的是 J 中内置了一个原语来计算笛卡尔积,monad {
。
例如:
> { 2 # <1 0 _1 NB. Or i:_1 instead of 1 0 _1
1 1
1 0
1 _1
0 1
0 0
0 _1
_1 1
_1 0
_1 _1
盘点
请注意,monad {
的输入是一个框列表,该列表中框的数量决定了每个组合中元素的数量。包含两个框的列表生成一个二元组数组,包含三个框的列表生成一个三元组数组,依此类推。
有点过分
鉴于完整的外积(笛卡尔积)如此昂贵(O(n^m)
),有人不禁要问:为什么 J 对此有原语?
当我们检查 monad {
的输出时,也会出现类似的疑虑:为什么它是装箱的?当且仅当我们想要合并不兼容类型和形状的数组时,方框才会在 J 中使用。但是根据 {
.
{ y
的所有结果都将具有相同的类型和形状
那么,是什么原因呢?好吧,事实证明这两个问题是相关的,并且是合理的,一旦我们理解了为什么首先引入 monad {
。
我对此感到矛盾
我们必须记住,J 中的所有动词都必然是矛盾的。 J 的语法不允许只有单子或二元的动词。当然,一个价或另一个价可能有一个空域(即没有有效输入,如 monad E.
或 dyad ~.
或 [:
的任一价),但它仍然 存在。
具有空域的价是 "real",但它的有效输入范围是空的(有效输入范围的想法的扩展 +
是数字,以及任何东西否则,像字符一样,产生 "domain error").
好的,好的,所以所有的动词都有两个价,那又怎样?
精选历史
嗯,肯·艾弗森 (Ken Iverson) 对 J 的主要设计目标之一,在长期使用 APL 后,放弃了数组索引的括号表示法(例如 A[3 4;5 6;2]
),并认识到 select来自数组的离子是一个函数。
这是一个巨大的洞察力,对语言的设计和使用都产生了严重影响,不幸的是我没有 space 进入这里。
并且由于所有函数都需要一个名称,我们必须给 selection 函数起一个名字。 J 中的所有原始动词都拼写为字形、变形字形(在我看来,.
、:
、.:
等后缀是变音符号)或变形字母数字。
现在,因为 selection 所以 是面向数组编程的常见和基础,它被赋予了一些主要的房地产(J 正字法中的区别标志), 单字符字形:{
²。
因此,由于 {
被定义为 selection,而 selection 当然是二元的(即有两个参数:索引和数组),这说明对于二元组 {
。现在我们明白了为什么要注意所有动词都是矛盾的。
我想我正在接受一个主题
在设计语言时,最好给 monad {
一些与 "selection" 的主题关系;出于优雅和助记的目的,将动词的两个价在主题上联系起来是 J 中的常见模式。
这个广泛的模式也是一个值得单独讨论的话题,但现在让我们关注为什么选择目录/笛卡尔积作为 monad {
。有什么关系?是什么导致了另一个怪癖,即它的结果总是被装箱?
托槽切除术
好吧,请记住引入 {
是为了取代——完全取代——APL 和(以及许多其他编程语言和符号)的旧括号下标语法。这立即使 selection 变得更容易、更有用,并且还简化了 J 的语法:在 APL 中,语法和解析器必须具有特殊的索引规则,例如:
A[3 4;5 6;2]
语法异常。但是,天哪,从程序员的角度来看,这不是很有用且富有表现力吗?
但这是为什么呢?是什么导致了多维括号表示法的经济性?这么短的时间怎么能说这么多space?
好吧,让我们看看我们在说什么。在上面的表达式 A[3 4;5 6;2]
中,我们要求第 3rd 和第 4th 行,第 5第 th 和第 6th 列,以及第 2nd 平面。
也就是我们要
- 平面 2,第 3 行,第 5 列,并且
平面 2,第 3 行,第 6 列,并且
平面 2,第 4 行,第 5 列和
- 平面 2,第 4 行,第 6 列
想一想。我会等。
Ken 让你大吃一惊的时刻
轰,对吧?
Indexing is a Cartesian product.
一直都是。但是 Ken 看到了。
所以,现在,在 APL 中我们不说 A[3 4;5 6;2]
(有些人挥手说 []IO
是 1
还是 0
),我们说:
(3 4;5 6;2) { A
当然,这只是 shorthand,或语法糖,用于:
idx =. { 3 4;5 6;2 NB. Monad {
idx { A NB. Dyad {
所以我们保留了熟悉的、方便的、暗示性的分号语法(what do you want to bet being spelled ;
is also not a巧合?)同时获得将 {
变成第一个 class 函数的所有好处,因为它本来应该是³。
打开神秘盒子
这让我们回到另一个最后的狡辩。 如果 monad {
的结果在类型和形状上都是规则的,那为什么还要装箱?那岂不是多余又不方便?
嗯,是的,但请记住,x { y
中的未装箱即数字 LHA 仅 selects 项目 来自 y
。
这很方便,因为经常需要多次 select 相同的项目(例如,将 'abc'
替换为 'ABC'
并将任何非 abc 字符默认为 '?'
,我们通常会说 ('abc' i. y) { 'ABC','?'
,但这只有效,因为我们被允许 select 多次索引 4,即 '?'
。
但是这种便利性排除了使用直接数值数组来也进行多维索引。也就是说,未装箱的数字对 select 项目(最常见的用例)的便利性干扰了 also 使用未装箱的数字数组来表达,例如A[17;3;8]
通过 17 3 8 { A
。我们不能两全其美。
所以我们需要一些其他符号来表达多维 select 离子,并且由于二元体 {
的左秩为 0(正是 因为 前述),并且单个原子盒可以封装任意结构,盒是完美的候选者。
所以,为了表达 A[17;3;8]
而不是 17 3 8 { A
,我们简单地说 (< 17;3;8) { A
,这又是直接、方便和熟悉的,并且允许我们做任意数量的同时存在多维 select 离子,例如( (< 17;3;8) , (<42; 7; 2) { A
),这是您在面向数组的语言中想要和期望的。
这意味着,当然,为了产生 dyad {
期望作为输入的那种输出,monad {
必须产生 boxes ⁴。 QED.
哦,还有 PS:正如我所说,装箱允许单个原子中的 任意 结构,如果我们不装箱会发生什么,甚至是一个盒子列表,但盒子是一个盒子?好吧,你有没有想过用一种方式来表达 "I want every index except the last" 或第 3 位,或第 42 位和第 55 位?嗯...
脚注:
¹ 请注意,在算术 tables +/~ i.10
和 */~ i.12
中,我们可以省略显式 "0
(出现在 ,"0/~ _1 0 1
) 因为算术动词已经是标量的(显然)
² 但是为什么 selection 给出了 那个特定的 字形,{
?
好吧,Ken 故意不透露 J 的正字法中使用的具体助记符选择,因为他不想为他的用户口述这样的个人选择,但对我来说,Dan,{
看起来像一个从右向左指向的小漏斗。也就是右边的数据流很大,左边的数据流小很多,像水龙头滴水一样。
同样,我也经常看到二元体|:
像一点咖啡table或者巨石阵三体巨石被踢翻在一边,即转置.
而monad #
显然是助记符(count, tally, 项目数量),但对偶总是对我很有启发性,因为它看起来像一个小网,保留感兴趣的项目并让其他一切"fall through".
但是,当然,YMMV。这正是 Ken 从未为我们详细说明的原因。
³ 您是否还注意到,在 APL 中,作为控制数据的索引列在数组的右侧,而在 J 中它们现在在左边,控制数据属于哪里?
⁴ 虽然这个 Jer 仍然希望看到 monad {
产生未装箱的结果,但代价是 J 引擎内的一些额外复杂性,即以单个实现者为代价,并使语言的每个用户受益
n 有很多有趣的文献更详细地介绍了这个material,但不幸的是我现在没有时间去把它挖出来。如果有足够的兴趣,我可能会稍后回来编辑参考答案。现在,值得一读 Mastering J, an early paper on J by one of the luminaries of J, Donald McIntyre, which makes mention of the eschewing of the "anomalous bracket notation" of APL, and perhaps a tl;dr version of this answer I personally posted to the J forums in 2014.