我有一个功能
slow :: Double -> Double
它经常被调用(数亿次),但只被调用大约一千个离散值。这似乎是一个很好的记忆候选者,但我不知道如何记忆一个 Double 的函数。
制作列表的标准技术不起作用,因为它不是整数类型。我查看了 Data.MemoCombinators,但它本身并不支持 Doubles。有一个bits
用于处理更多数据类型的函数,但Double
不是Data.Bits
.
有没有一种优雅的方式来记忆“慢”?
我有一个功能
slow :: Double -> Double
它经常被调用(数亿次),但只被调用大约一千个离散值。这似乎是一个很好的记忆候选者,但我不知道如何记忆一个 Double 的函数。
制作列表的标准技术不起作用,因为它不是整数类型。我查看了 Data.MemoCombinators,但它本身并不支持 Doubles。有一个bits
用于处理更多数据类型的函数,但Double
不是Data.Bits
.
有没有一种优雅的方式来记忆“慢”?
你总是可以使用丑陋的备忘录。内部是不纯的,但它很快并且可以满足您的需求(除非参数是 NaN)。
我认为StableMemo
应该完全按照您的意愿行事,但我对此没有任何经验。
有两种主要方法:使用Ord
属性将键存储在树结构中,例如Map
. 这不需要您需要的整体属性,例如MemoTrie
方法;因此速度较慢但非常简单。
另一种适用于更通用类型的替代方法是使用Hash 函数无序映射到一个大的整数域,以便将键存储在哈希映射中。这将大大加快速度,但几乎一样简单,因为 的界面HashMap
很大程度上与 的界面相匹配Map
,因此您可能希望采用这种方式。
现在,可悲的是,两者都不像MemoCombinators
. 它直接建立在IntTrie
,专门用于提供惰性/无限/纯接口。相比之下,两者Map
,特别是HashMap
,都可以很好地用于不纯的记忆,但天生就不能纯粹地做到这一点。你可能会扔一些UnsafePerformIO
(呃哦),或者只是在IO
monad 中公开地做(yuk!)。或使用StableMemo
.
但是,如果您已经知道它将是哪些值,至少在大多数调用中,在 compile-time ,它实际上是简单和安全的。然后,您可以在开始时用这些值填充本地哈希映射,并在每次调用时查找它是否存在,否则只需直接调用昂贵的函数:
import qualified Data.HashMap.Lazy as HM
type X = Double -- could really be anything else
notThatSlow :: Double -> X
notThatSlow = \v -> case HM.lookup v memo of
Just x -> x
Nothing -> slow v
where memo = HM.fromList [ (v, x) | v<-expectedValues, let x = slow v ]