【发布时间】:2018-12-01 23:03:05
【问题描述】:
我决定用 F# 来解决 Advent of Code 2018 的第一天的第二个问题(执行循环求和并找到第一个重复求和),但表现力不足,我找不到减速的原因。
在 Python 3 中解决的问题
For a given input 总计约 140,000 次,此代码在几秒钟内执行。
data = list(map(int, '''
+1
-1
'''.strip().splitlines()))
from itertools import cycle, accumulate
class superset(set):
def add(self, other):
super().add(other)
return other
def mapwhile(func, pred, iterable):
for i in iterable:
if not pred(i):
yield func(i)
return
yield func(i)
def last(iterable):
return list(iterable)[-1]
s = superset([0])
print(last(mapwhile(s.add, lambda x: x not in s, accumulate(cycle(data)))))
用 F# 解决问题
我在匹配表达式上添加了一个条件断点,以每千分之一计时一次i,看来这段代码执行约 100 和/秒,即使在一小时后也无法解决。以荒谬的数量级急剧放缓。
let input = @"
+1
-1
"
let cycle xs = seq { while true do yield! xs }
let accumusum xs = Seq.scan(fun acc elem -> acc + elem) 0 xs
let rec findfreqcycle i (s:int Set) (data:int seq) =
let head, tail = Seq.head data, Seq.tail data
match s.Contains(head) with
| false -> findfreqcycle (i+1) (s.Add(head)) (tail)
| true -> head
let data = input.Trim().Split('\n') |> Seq.map int |> Seq.toList |> cycle
accumusum data |> findfreqcycle 0 Set.empty
据我所知,每个代码示例背后的核心思想或多或少是相同的。 输入只被急切地解析一次,生成器函数/序列懒惰地重复每个数字。
唯一的区别是,在 F# 示例中,实际查找第一个重复求和的函数是递归的。内存分析表明内存使用几乎恒定,并且尾递归处于活动状态。
我可能做错了什么,如何更好地分析这些递归和生成函数的性能?
【问题讨论】:
-
听起来我的代码示例是垃圾收集的热点——
yield!、Seq.head、Seq.tail都会产生新的 IEnumerables。我什至不知道我打算如何解决这个问题。至少yield!不在热循环中。希望该套装也不会造成问题。