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](有些人挥手说 []IO1 还是 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.