我该如何选择性地对数组的多个轴进行求和?

Ale*_*ley 7 arrays j

J中用于选择性地求和数组的多个轴的首选方法是什么?

例如,假设这a是以下3级数组:

   ]a =: i. 2 3 4
 0  1  2  3
 4  5  6  7
 8  9 10 11

12 13 14 15
16 17 18 19
20 21 22 23
Run Code Online (Sandbox Code Playgroud)

我的目标是定义一个二元"sumAxes"来总结我选择的多个轴:

   0 1 sumAxes a      NB. 0+4+8+12+16+20 ...
60 66 72 78

   0 2 sumAxes a      NB. 0+1+2+3+12+13+14+15 ...
60 92 124

   1 2 sumAxes a      NB. 0+1+2+3+4+5+6+7+8+9+10+11 ...
66 210
Run Code Online (Sandbox Code Playgroud)

我目前正在尝试实现这个动词的方法是使用dyad |:首先置换轴a,然后在求和结果项之前使用,"n(n我想要求和的数字轴在哪里)对所需等级的项进行求解:

   sumAxes =: dyad : '(+/ @ ,"(#x)) x |: y'
Run Code Online (Sandbox Code Playgroud)

这看起来像我想要的那样工作,但作为JI的初学者,我不确定我是否会忽略排名或特定动词的某些方面,以便能够更清晰地定义.更一般地说,我想知道在这种语言中,置换轴,ravelling和求和是否是惯用的或有效的.

对于上下文,我之前使用Python的NumPy库的大部分数组编程经验.

NumPy没有J的排名概念,而是希望用户明确标记数组的轴以减少:

>>> import numpy
>>> a = numpy.arange(2*3*4).reshape(2, 3, 4) # a =: i. 2 3 4
>>> a.sum(axis=(0, 2))                       # sum over specified axes
array([ 60,  92, 124])
Run Code Online (Sandbox Code Playgroud)

作为一个脚注,当我sumAxes指定单个轴时(由于rank不能与"axis"互换),我当前的实现具有与NumPy相比"不正确"工作的缺点.

Dan*_*ron 10

动机

J具有处理任意排序数组的难以置信的设施.但是这种语言的一个方面同时几乎普遍有用和合理,但也与这种维度不可知的本质有些对立.

主轴(实际上,通常引导轴)是隐式特权的.这是作为基础的概念,例如#作为项目数量(即第一轴的维度),+/没有进一步修改的低调优雅和一般性,以及该语言的许多其他美丽部分.

但这也是解决这个问题时遇到的障碍的原因.

标准方法

所以解决问题的一般方法就像你拥有它一样:转置或重新排列数据,使你感兴趣的轴成为引导轴.你的方法是经典和无懈可击的.你可以在良心上使用它.

替代方法

但是,就像你一样,它让我有点尴尬,我们被迫在类似情况下跳过这样的箍.我们有点反对语言的一个线索就是结合的动态论证"(#x); 通常连词的参数是固定的,并且在运行时计算它们通常会迫使我们使用显式代码(如在您的示例中)或显着更复杂的代码.当语言难以做到的时候,这通常是你正在削减粮食的一个标志.

另一个是ravel(,).这不只是我们想要转置一些轴; 我们希望将焦点放在一个特定的轴上,然后将所有跟随它的元素运行到一个平面向量中.虽然我实际上认为这更多地反映了我们如何构建问题所带来的限制,而不是符号中的一个.更多关于这篇文章的最后一节.

有了这个,我们可能觉得我们希望直接解决非引导轴.而且,在这里和那里,J提供的原语允许我们完全这样做,这可能暗示语言的设计者也觉得需要在引导轴的首要地位中包含某些例外.

介绍性的例子

例如,二元|.(旋转)具有等级1 _,即它在左侧采用向量.

对于那些已经使用它多年的人来说,这有时令人惊讶,从来没有超过左边的标量.与未绑定的右等级一起,是J的主轴偏差的另一个微妙结果:我们将正确的参数视为向量,将左参数视为该向量的简单标量旋转值.

从而:

   3 |. 1 2 3 4 5 6
4 5 6 1 2 3
Run Code Online (Sandbox Code Playgroud)

   1 |. 1 2 , 3 4 ,: 5 6
3 4
5 6
1 2
Run Code Online (Sandbox Code Playgroud)

但是在后一种情况下,如果我们不想将表视为向量,而是作为向量,该怎么办?

当然,经典的方法是使用rank来明确表示我们感兴趣的轴(因为隐藏它总是选择引导轴):

   1 |."1 ] 1 2 , 3 4 ,: 5 6
2 1
4 3
6 5
Run Code Online (Sandbox Code Playgroud)

现在,这在J代码中完全是惯用的,标准的,无处不在的:J鼓励我们按等级来思考.没有人会眨眼睛阅读这段代码.

但是,正如一开始所描述的那样,在另一种意义上,它可以感觉像是一个警察或手动调整.特别是当我们想要在运行时动态选择排名时.在符号上,我们现在不再是整个数组,而是解决每一行.

这就是左等级的|.来源:它是可以直接处理非主导轴的少数基元之一.

   0 1 |. 1 2 , 3 4 ,: 5 6
2 1
4 3
6 5
Run Code Online (Sandbox Code Playgroud)

看马,没有等级!当然,我们现在必须独立地为每个轴指定一个旋转值,但这不仅可以,它很有用,因为现在左边的参数更像是可以从输入计算的东西,真正的J精神.

直接求和非引导轴

所以,既然我们知道J允许我们在某些情况下解决非主导轴,我们只需要调查这些情况并找出一个看起来适合我们目的的情况.

我发现最常用于非前导轴工作的原语是;.带有盒装的左手参数.所以我的直觉就是先达到目标.

让我们从您的示例开始,稍加修改以查看我们的总结.

    ]a =: i. 2 3 4
    sumAxes =: dyad : '(< @ ,"(#x)) x |: y'

     0 1 sumAxes a
+--------------+--------------+---------------+---------------+
|0 4 8 12 16 20|1 5 9 13 17 21|2 6 10 14 18 22|3 7 11 15 19 23|
+--------------+--------------+---------------+---------------+ 
     0 2 sumAxes a
+-------------------+-------------------+---------------------+
|0 1 2 3 12 13 14 15|4 5 6 7 16 17 18 19|8 9 10 11 20 21 22 23|
+-------------------+-------------------+---------------------+
    1 2 sumAxes a
+-------------------------+-----------------------------------+
|0 1 2 3 4 5 6 7 8 9 10 11|12 13 14 15 16 17 18 19 20 21 22 23|
+-------------------------+-----------------------------------+
Run Code Online (Sandbox Code Playgroud)

对于衍生自;.1和朋友的对子的定义的相关部分是:

在二进箱子音柱1,_1,2,和_2通过在布尔矢量的1S确定x; 空向量x和非零#y表示整个y.如果x是原子01它被视为(#y)#x.通常,布尔向量>j{x指定如何j切割轴,将原子视为(j{$y)#>j{x.

这意味着:如果我们只是尝试沿着它的维度切割数组而没有内部分割,我们可以简单地使用带有仅由1s和a:s 组成的左参数的二元切割.1向量中的s 数(即总和)决定了结果数组的等级.

因此,重现上面的例子:

     ('';'';1) <@:,;.1 a
+--------------+--------------+---------------+---------------+
|0 4 8 12 16 20|1 5 9 13 17 21|2 6 10 14 18 22|3 7 11 15 19 23|
+--------------+--------------+---------------+---------------+
     ('';1;'') <@:,;.1 a
+-------------------+-------------------+---------------------+
|0 1 2 3 12 13 14 15|4 5 6 7 16 17 18 19|8 9 10 11 20 21 22 23|
+-------------------+-------------------+---------------------+
     (1;'';'') <@:,;.1 a
+-------------------------+-----------------------------------+
|0 1 2 3 4 5 6 7 8 9 10 11|12 13 14 15 16 17 18 19 20 21 22 23|
+-------------------------+-----------------------------------+
Run Code Online (Sandbox Code Playgroud)

瞧瞧.另外,请注意左手参数中的模式?两个ace 正好在你原来的召唤指数上sumAxe.看看我的意思是,在J精神中为每个维度提供一个嗅觉就像一件好事的价值?

因此,使用此方法为sumAxe相同的接口提供模拟:

   sax =: dyad : 'y +/@:,;.1~ (1;a:#~r-1) |.~ - {. x -.~ i. r=.#$y'     NB. Explicit
   sax =: ]  +/@:,;.1~  ( (] (-@{.@] |. 1 ; a: #~ <:@[) (-.~ i.) ) #@$) NB. Tacit
Run Code Online (Sandbox Code Playgroud)

结果为简洁而省略,但它们与您的相同sumAxe.

最后的考虑

还有一件事我想指出.sumAxe从Python调用的调用接口命名了你想要"一起运行"的两个轴.这绝对是看待它的一种方式.

另一种看待它的方式,它借鉴了我在这里提到的J哲学,就是命名你想要求和的轴.事实上,这是我们的实际焦点,这一事实证明了我们对每个"切片"的理解,因为我们不关心它的形状,只关心它的价值.

在谈论你感兴趣的事物的观点上的这种变化,有一个优点,它总是一件事,而这个奇点允许我们的代码中的某些简化(再次,特别是在J,我们通常谈论[新的] ,即后转置]引导轴)¹.

让我们再看一下我们的那些 - 和 - aces向量参数;.,来说明我的意思:

     ('';'';1) <@:,;.1 a
     ('';1;'') <@:,;.1 a
     (1;'';'') <@:,;.1 a
Run Code Online (Sandbox Code Playgroud)

现在将三个带括号的参数视为三行的单个矩阵.你有什么突出的?对我来说,它是沿着反对角线的那些.它们数量较少,并且具有价值; 相比之下,aces形成矩阵的"背景"(零).这些是真实的内容.

这与我们的sumAxe界面现在的情况形成对比:它要求我们指定aces(零).相反,我们如何指定1,即实际感兴趣的轴

如果我们这样做,我们可以重写我们的功能:

  xas  =: dyad : 'y +/@:,;.1~ (-x) |. 1 ; a: #~ _1 + #$y'  NB. Explicit
  xas  =: ]  +/@:,;.1~  -@[ |. 1 ; a: #~ <:@#@$@]          NB. Tacit
Run Code Online (Sandbox Code Playgroud)

而不是打电话0 1 sax a,你打电话2 xas a,而不是0 2 sax a,你打电话1 xas a,等等.

这两个动词的相对简单性表明J同意这种焦点反转.


¹ 在这段代码中我假设你总是希望折叠除1以外的所有轴.这个假设是用我生成的 - 和 - aces向量的方法编码的|..

但是,sumAxes当仅指定单个轴时,与NumPy相比,您的脚注具有"错误"工作的缺点,建议有时您只想折叠一个轴.

这是完全可能的,并且该;.方法可以采取任意(原位)切片; 我们只需要改变我们指示它的方法(生成1s-and-aces向量).如果您提供了一些您想要的概括示例,我将在此处更新帖子.可能只是使用(<1) x} a: #~ #$y((1;'') {~ (e.~ i.@#@$))代替(-x) |. 1 ; a:#~<:#$y.

  • 这是太棒了!感谢您对J的所有这些见解以及查看问题的其他方法.我需要重读这几次并做一些实验来获得"."的悬念,但我开始看到它是如何适合我想要做的.我非常欣赏以J方式构建函数的观点.再次感谢! (3认同)