Haskell 记忆以集合为参数的函数
我正在使用Haskell 记忆以集合为参数的函数,haskell,memoization,Haskell,Memoization,我正在使用Data.memombinators()来记忆一个函数,该函数将一个集合作为其参数并返回一个集合(这是一个精心设计的示例,它什么都不做,但需要很长时间才能完成): 由于Data.memo Combinators未实现集合表,因此我想编写自己的: {-# LANGUAGE RankNTypes #-} import Data.MemoCombinators (Memo) import qualified Data.MemoCombinators as Memo import Data.
Data.memombinators
()来记忆一个函数,该函数将一个集合作为其参数并返回一个集合(这是一个精心设计的示例,它什么都不做,但需要很长时间才能完成):
由于Data.memo Combinators
未实现集合表,因此我想编写自己的:
{-# LANGUAGE RankNTypes #-}
import Data.MemoCombinators (Memo)
import qualified Data.MemoCombinators as Memo
import Data.Set (Set)
import qualified Data.Set as Set
set :: Ord a => Memo a -> ((Set a) -> r) -> (Set a) -> r
set m f = Memo.list m (f . Set.fromList) . Set.toList
这是我的测试
,应该记住:
test s = set Memo.integral test' s
where
test' s = case Set.toList s of
[] -> Set.singleton 0
[x] -> Set.singleton 1
(x:xs) -> test (Set.singleton x) `Set.union` test (Set.fromList xs)
我不清楚数据的文档,所以基本上我不知道自己在做什么
我的问题是:
Memo.list
函数的第二个参数是什么?它是列表元素的记忆器吗
如何直接为集合实现表格,而不使用Memo.list
?下面是我想弄清楚如何在不使用别人的库的情况下手动实现备忘录。例如,使用贴图
。我见过使用无限列表记忆整数的例子,但对于映射,我不知道如何初始化映射以及如何插入映射
谢谢您的帮助。您的测试功能很好,不过通常您会使用Set操作将测试定义为Set上的函数。下面是我所说的一个例子:
-- memoize a function on Set Int
foo = set M.integral foo'
where foo' s | Set.null s = 0
foo' s = let a = Set.findMin s
b = Set.findMax s
m = (a+b) `div` 2
(lo,found,hi) = Set.splitMember m s
in if a >= b
then 1
else (if found then 1 else 0) + foo lo + foo hi
这是一种计算集合中元素数量的非常低效的方法,但请注意,foo'
是如何根据集合操作定义的
还有其他问题:
Memo.list函数的第二个参数是什么?它是列表元素的记忆器吗
Memo.list
具有签名Memo a->Memo[a]
,因此在表达式Memo.list mf
中我们有:
m :: Memo a
f :: [a] -> r -- some type r
Memo.list m f :: [a] -> r
因此,f
是您正在记忆的[a]
上的函数,m
是一个函数的记忆器,函数的参数类型为a
如何直接为集合实现表
这取决于你所说的“直接”是什么意思。以这种方式进行记忆将涉及到创建一个(可能是无限的)惰性数据结构。字符串
、积分
和列表
备忘录都使用某种形式的惰性trie。这与命令式语言中的记忆非常不同,在命令式语言中,您显式检查哈希映射以查看是否已经计算了某些内容,并使用函数的值更新该哈希映射,等等。(顺便说一下,您可以在ST或IO单元格中进行这种记忆,它甚至比数据更好。
通过传递到列表来记忆设置->r
函数是一个好主意,但我会使用to/from AscList:
set m f = Memo.list m (f . Set.fromAscList) . Set.toAscList
这样,setset.fromList[3,4,5]
将重新使用trie的同一部分,该部分是为记忆set.fromList[3,4]
的值而创建的
Memo.list函数的第二个参数是什么?它是列表元素的记忆器吗
第一个参数m
是列表元素的记忆器。第二个参数f
是要应用于列表的函数(也将被记忆)
如何在不使用Memo.list的情况下直接为集合实现一个表?下面是我们想要了解的实现方法
在不使用某人的库的情况下手动记录。例如,
使用映射。我见过使用
无限列表,但在地图的情况下,我不知道如何
初始化地图以及如何插入地图
使用与Data.memo combinator
相同的策略,您可以执行与列表类似的操作。这种方法不使用显式数据结构,而是探索Haskell将内容保存在内存中的方式和惰性计算
set :: Ord a => Memo a -> Memo (Set a)
set m f = table (f Set.empty) (m (\x -> set m (f . (x `Set.insert`))))
where
table nil cons set | Set.null set = nil
| otherwise = uncurry cons (Set.deleteFindMin set)
您还可以在Haskell中使用显式数据结构(如映射
)来使用memonization。我将使用Fibonacci示例来演示这一点,因为它更容易进行基准测试,但对于其他函数也类似
让我们从简单的实现开始:
fib0 :: Integer -> Integer
fib0 0 = 0
fib0 1 = 1
fib0 x = fib0 (x-1) + fib0 (x-2)
import qualified Data.MemoCombinators as Memo
fib1 :: Integer -> Integer
fib1 = Memo.integral fib'
where
fib' 0 = 0
fib' 1 = 1
fib' x = fib1 (x-1) + fib1 (x-2)
然后提出了这一实施方案:
fib0 :: Integer -> Integer
fib0 0 = 0
fib0 1 = 1
fib0 x = fib0 (x-1) + fib0 (x-2)
import qualified Data.MemoCombinators as Memo
fib1 :: Integer -> Integer
fib1 = Memo.integral fib'
where
fib' 0 = 0
fib' 1 = 1
fib' x = fib1 (x-1) + fib1 (x-2)
最后,我的版本使用Map
:
import Data.Map (Map)
import qualified Data.Map as Map
fib2 :: Integer -> Integer
fib2 = fst . fib' (Map.fromList [(0, 0),(1, 1)])
where
fib' m0 x | x `Map.member` m0 = (Map.findWithDefault 0 x m0, m0)
| otherwise = let (v1, m1) = fib' m0 (x-1)
(v2, m2) = fib' m1 (x-2)
y = v1 + v2
in (y, Map.insert x y m2)
现在,让我们看看他们的表现:
fib0 40: 13.529371s
fib1 40: 0.000121s
fib2 40: 0.000048s
fib0
已经太慢了。让我们用另外两个做一个适当的测试:
fib1 400000: 6.234243s
fib2 400000: 4.022798s
fib1 500000: 8.683649s
fib2 500000: 5.781104s
对于我执行的所有测试,Map
解决方案似乎实际上比Memo
解决方案的性能要好。但我认为数据的最大优势。MemoCombinators
实际上具有如此优异的性能,而无需编写比原始解决方案多得多的代码
更新:我更改了结论,因为我没有正确执行基准测试。我在同一执行中执行了几个调用,在500000的情况下,无论第二个调用是什么(无论是fib1
还是fib2
),这花费的时间太长。您的fib
示例很好,但请注意,创建的映射在调用fib2
之间没有保留。此外,您应该尝试使用Data.IntMap
来查看它的性能是否优于Data.map
。