可以在J中修改列表的元素



我一直在玩J中lookandsay(OEIS A005150)的实现。我制作了两个版本,都非常简单,使用while.类型的控制结构。一个循环,另一个循环。因为我有强迫症,我开始对版本进行比较计时。

看,说是序列1 11 21 1211 111221,一个一,两个一,等等

对于列表的早期元素(最多20个左右),循环版本获胜,但仅以很小的数量获胜。时间在30左右会导致递归版本获胜,如果堆栈空间足以支持递归版本,那么递归版本可能会更受欢迎。我研究了原因,我认为这与处理中间结果有关。序列中的第30个数字有5808位数字。(第32位,9898位,第34位,16774位。)

当您使用递归处理问题时,您可以将中间结果保存在递归调用中,并且最后的拆堆会构建结果,以便对结果的处理最少。

在列表版本中,您需要一个变量来保存结果。每次循环迭代都需要向结果中添加两个元素。

在我看来,问题是我在J中找不到任何方法来修改现存的数组而不完全重新分配它

try. o =. o,e,(0&{y) catch. o =. e,(0&{y) end.

将一个元素放入o中,当我们开始时,o可能没有值。这可能比慢得多

o =. i.0
.
.
.
o =. (,o),e,(0&{y)

关键是,如果没有拉锯,结果会得到错误的形状,或者看起来是这样。它以某种方式继承了i.0的形状。

但是,即使是像}modify这样的函数也不会修改列表,它们会返回一个对其进行了修改的列表,如果你想保存列表,你需要分配它。随着分配列表的大小增加(当你从开始到结束遍历数字以生成下一个数字时),分配似乎需要越来越多的时间。这个赋值实际上是我能看到的唯一一个东西,它会使元素329988位数字在递归版本中花费更少的时间,而元素20(408位数字)在循环版本中花费较少的时间。

递归版本使用构建返回

e,(0&{y),(,lookandsay e }. y)

上面的行既是函数的返回行,也是递归的返回行。因此,当调用到达字符串的末尾,所有内容都不稳定时,整个返回向量会立即构建。

在APL中,我认为人们可以说一些关于的内容

a[1+rho a] <- new element

但当我在NARS2000中尝试此操作时,我发现它会导致索引错误。我没有任何其他APL,我可能还记得APL Plus中的这个成语,我怀疑它在APL\360或APL\1130中是这样起作用的。我可能完全记错了。

我在J中找不到这样做的方法。可能没有办法做到这一点,但下一个想法是预先分配一个可以保存结果的数组,并更改单个条目。我也没有办法做到这一点——也就是说,J似乎不支持APL习语:

a<- iota 5
a[3] <- -1

这是因为语言纯洁而被禁止的副作用之一吗?

解释器是否将a=. a,foo或其某些变体识别为应该在内部快速路径到a[>:#a]=.foo的东西?

这是递归版本,只是为了好玩。我尝试了很多不同的版本,我相信程序越长,速度越慢,通常情况下,越复杂,速度就越慢。一般来说,程序可以被链接,这样如果你想要第n个数字,你就可以进行lookandsay^:n]y。我已经尝试了很多优化,但我遇到的问题是,我无法判断我将输出发送到什么环境中。如果我能告诉我要把它发送到程序的下一次迭代,我会把它作为一个数字数组而不是一个大数字发送。

我还怀疑,如果我能想出如何制作一个默认版本的代码,它会运行得更快,因为我发现,当我向代码中添加一些应该使其更短的东西时,它运行得更长。

lookandsay=: 3 : 0
if. 0 = # ,y do.  return. end. NB. return on empty argument
if. 1 ~: #@$ y do.  NB. convert rank 0 argument to list of digits
y =. (10&#.^:_1) x: y
f =. 1
assert. 1 = #@$ y NB. the converted argument must be rank 1
else.
NB. yw =. y
f =. 0
end.
NB. e should be a count of the digits that match the leading digit.
e=.+/*./y=0&{y
if. f do.
o=. e,(0&{y),(,lookandsay e }. y)
assert. e = 0&{ o
10&#. x: o
return.
else. 
e,(0&{y),(,lookandsay e }. y)
return.
end.
)

我对产生的数字的特征很感兴趣。我发现,如果你从1开始,数字永远不会超过3。如果你从一个高于3的数字开始,它将作为一个单例存在,你也可以通过从888888888这样的数字开始在生成的数字中获得一个数字,它将生成一个包含一个9和一个8的数字。但除了单身汉,没有一个数字会超过3。

编辑:我又做了一些测量。我最初编写的程序接受向量或标量,想法是在内部使用向量。我曾想过将一个向量从一层代码传递到另一层代码,但我仍然可能使用左参数来控制代码。当我通过顶级矢量时,代码运行速度会快得多,所以我的猜测是,大部分cpu都被从矢量到数字的超长数字所消耗。递归例程在递归时总是传递一个向量,这可能就是它几乎和循环一样快的原因。

这并不能改变我的问题。

我有一个答案,我三个小时都无法发布。我会把它贴出来的,请不要做大量的研究来回答它。

分配,如

arr=. 'z' 15} arr

执行到位。(有关其他支持的就地操作,请参阅JWiki文章)解释器确定只有arr的一小部分被更新,并且不创建要重新分配的整个新列表。

在您的情况下,发生的不是数组被重新分配,而是它以小增量多次增长,从而导致内存分配和重新分配。

如果您预先分配(通过给它分配一些大的数据块),那么您可以用}修改它,而不会受到太多惩罚。

在我问了这个问题之后,老实说,我已经忘记了这个网站。

是的,答案是该语言没有表示"更新到位"的形式,但如果你使用两种形式

x=:x,大多数

x=:大多数东西}x

那么解释器将这些识别为特殊的,并在适当的位置进行更新,除非它不能。口译员还可以识别出许多其他特殊功能,如:

199(1000&|@^)199

这种组合运算就是模幂运算。它从不计算整个求幂,因为

199(1000&|^)199

如果没有@,就以_结束。

所以值得一读关于特价商品的文章。我会把别人的答案标记出来。

sverre在上面提供的链接(http://www.jsoftware.com/jwiki/Essays/In-Place%20Operations)显示了支持修改现有数组而不是创建新数组的各种操作。它们包括:

myarray=: myarray,'blah'

如果你对lookandsay序列的默认版本感兴趣,请参阅此提交给RosettaCode:

las=: ,@((# , {.);.1~ 1 , 2 ~:/ ])&.(10x&#.inv)@]^:(1+i.@[)
5 las 1
11 21 1211 111221 312211

相关内容

  • 没有找到相关文章

最新更新