我一直在使用 J 中的 lookandsay (OEIS A005150) 的实现。我制作了两个版本,都非常简单,使用while.
类型控制结构。一个重复,另一个循环。因为我是强迫症,我开始在版本上运行比较计时。
看看并说是序列 1 11 21 1211 111221 that s,一一,二一,等等。
对于列表的早期元素(最多约 20 个),循环版本获胜,但数量很少。大约 30 的时间会导致递归版本获胜,如果堆栈空间足够支持递归版本,则可能会优先选择递归版本。我研究了原因,我相信这与处理中间结果有关。序列中的第 30 个数字有 5808 位。(第 32 个数字,9898 位,第 34 个,16774。)
当你用递归解决问题时,你可以在递归调用中保存中间结果,最后的unstacking构建结果,以便对结果进行最少的处理。
在列表版本中,您需要一个变量来保存结果。每次循环迭代都会导致您需要向结果中添加两个元素。
正如我所看到的,问题是我无法在 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 继承了一个形状。
但即使像 } amend 这样的函数也不会修改列表,它们会返回一个对其进行了修改的列表,如果要保存列表,则需要分配它。随着分配列表的大小增加(当您将数字从头到尾进行下一个数字时),分配似乎需要更多的时间和更多的时间。这个赋值确实是我唯一能看到的,它会使元素 32、9898 位在递归版本中花费更少的时间,而元素 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 都被从向量转换为数字的长数字所消耗。递归例程在递归时总是向下传递一个向量,这可能就是它几乎与循环一样快的原因。
这并没有改变我的问题。
我有一个答案,我不能发布三个小时。然后我会发布它,请不要做大量研究来回答它。