【问题标题】:Memoize a Clojure function that takes a lazy sequence as input记忆一个将惰性序列作为输入的 Clojure 函数
【发布时间】:2016-07-31 15:07:29
【问题描述】:

当 memoised 函数的参数是一个序列时,我如何让 memoize 工作

(defn foo
    ([x] (println "Hello First") (reduce + x))
    ([x y] (println "Hello Second") (reduce + (range x y))))


(def baz (memoize foo))

传递一个参数:

1)

(time (baz (range 1 1000000))) ;=> Hello First "Elapsed time: 14.870628 msecs"

2)

(time (baz (range 1 1000000))) ;=> "Elapsed time: 65.386561 msecs"

传递 2 个参数:

1)

(time (baz 1 1000000)) ;=> Hello Second "Elapsed time: 18.619768 msecs"

2)

(time (baz 1 1000000)) ;=> "Elapsed time: 0.069684 msecs"

传递 2 个参数时函数的第二次运行似乎是我所期望的。

但是使用矢量似乎可以工作...

(time (baz [1 2 3 5 3 5 7 4 6 7 4 45 6 7])) ;=> Hello First "Elapsed time: 0.294963 msecs"


(time (baz [1 2 3 5 3 5 7 4 6 7 4 45 6 7])) ;=> "Elapsed time: 0.068229 msecs"

【问题讨论】:

  • 嗯。如何处理是一件棘手的事情——为了比较的目的,需要消耗整个序列来确定身份,但是能够支持 ISeq 的优势之一是潜在的懒惰。所以我可以看到当前语义的论点——我们不想增加调用的前期成本(为了完全实现理论上可能是无限的序列)以潜在地启用缓存。
  • ...在实现层,问题是当序列用作映射中的键时会发生什么。 IE。序列是否以反映其内容的方式支持.hashCode(因此需要评估O(n)),以反映其身份的方式(这样相同的序列只会与自身比较),还是根本不支持?
  • @CharlesDuffy - 序列比较(哈希码等)的任何实现只能优化不等式的情况。所以自相矛盾的是,当memoize 被设计为有用时——当你在地图中有相等的键时——无论实现是什么,你最终都会得到 O(n) 比较时间。故事的寓意 - memoize 并非设计用于将长序列用作参数。

标签: clojure


【解决方案1】:

memoize 确实适用于序列,您只需要将苹果与苹果进行比较。 memoize 在先前使用的哈希图中查找参数,结果您最终会比较序列。比较长序列需要很长时间,无论它们是否是向量:

user> (def x (vec (range 1000000)))
;; => #'user/x
user> (def y (vec (range 1000000)))
;; => #'user/y
user> (time (= x y))
"Elapsed time: 64.351274 msecs"
;; => true
user> (time (baz x))
"Elapsed time: 67.42694 msecs"
;; => 499999500000
user> (time (baz x))
"Elapsed time: 73.231174 msecs"
;; => 499999500000

当您使用非常短的输入序列时,时间由函数内部的reduce 控制。但是很长的大部分时间你看到的实际上是memoize里面的比较时间。

所以从技术上讲,memoize 工作,所有序列都以相同的方式工作。但“技术上”工作并不意味着“有用”。正如您自己发现的那样,对于具有昂贵的比较语义的输入来说,它是无用的(实际上甚至可能有害)。您的第二个签名解决了这个问题。

【讨论】:

    猜你喜欢
    • 2013-11-28
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多