Data structures Haskell可变映射/树

Data structures Haskell可变映射/树,data-structures,haskell,hashtable,mutable,Data Structures,Haskell,Hashtable,Mutable,我正在寻找Haskell中的可变(平衡)树/映射/哈希表,或者如何在函数中模拟它。也就是说,当我多次调用同一个函数时,结构被保留。到目前为止,我已经尝试了Data.HashTable(虽然还可以,但速度有点慢)和Data.Array.Judy,但我无法使它与GHC 6.10.4一起工作。还有其他选择吗?尽管您要求可变类型,但我建议您使用不可变的数据结构,并将后续版本作为参数传递给函数 关于使用哪种数据结构 在肯特有一家餐馆 如果您有整数键,Data.IntMap非常有效 如果您有字符串键,则字

我正在寻找Haskell中的可变(平衡)树/映射/哈希表,或者如何在函数中模拟它。也就是说,当我多次调用同一个函数时,结构被保留。到目前为止,我已经尝试了Data.HashTable(虽然还可以,但速度有点慢)和Data.Array.Judy,但我无法使它与GHC 6.10.4一起工作。还有其他选择吗?

尽管您要求可变类型,但我建议您使用不可变的数据结构,并将后续版本作为参数传递给函数

关于使用哪种数据结构

  • 在肯特有一家餐馆

  • 如果您有整数键,
    Data.IntMap
    非常有效

  • 如果您有字符串键,则字符串看起来非常好


问题是我不能使用(或者我不知道如何)不可变的类型

如果幸运的话,可以将表数据结构作为额外参数传递给每个需要它的函数。但是,如果您的表需要广泛分发,您可能希望使用状态为表内容的

如果你正在尝试记忆,你可以尝试Conal Elliott博客中的一些惰性记忆技巧,但是一旦你超越整数参数,惰性记忆就会变得非常模糊,我不建议你作为初学者尝试。也许你可以发布一个关于你试图解决的更广泛问题的问题?对于Haskell和可变性,问题通常是如何在某种范围内包含突变或更新


在没有任何全局可变变量的情况下学习编程并不容易。

基于@Ramsey的答案,我还建议您重新接受函数,以获取映射并返回修改后的映射。然后使用好的ol编写代码,这在修改时非常有效。以下是一种模式:

import qualified Data.Map as Map

-- | takes input and a map, and returns a result and a modified map
myFunc :: a -> Map.Map k v -> (r, Map.Map k v)
myFunc a m = … -- put your function here

-- | run myFunc over a list of inputs, gathering the outputs
mapFuncWithMap :: [a] -> Map.Map k v -> ([r], Map.Map k v)
mapFuncWithMap as m0 = foldr step ([], m0) as
    where step a (rs, m) = let (r, m') = myFunc a m in (r:rs, m')
    -- this starts with an initial map, uses successive versions of the map
    -- on each iteration, and returns a tuple of the results, and the final map

-- | run myFunc over a list of inputs, gathering the outputs
mapFunc :: [a] -> [r]
mapFunc as = fst $ mapFuncWithMap as Map.empty
    -- same as above, but starts with an empty map, and ignores the final map

很容易抽象此模式,并使mapFuncWithMap在以这种方式使用映射的函数上成为泛型

如果我没有看错你的评论,那么你的结构可能需要计算~500k的总值。计算成本很高,因此您希望只执行一次,而在后续访问中,您只需要不重新计算的值

在这种情况下,利用Haskell的懒惰为你带来好处~500k并没有那么大:只需构建一张所有答案的地图,然后根据需要获取。第一次抓取将强制计算,相同答案的后续抓取将重用相同的结果,如果您从未抓取特定计算,则不会发生这种情况

您可以在文件中找到使用三维点距离作为计算的这个想法的一个小实现。该文件使用
Debug.Trace
记录实际完成计算的时间:

> ghc --make PointCloud.hs 
[1 of 1] Compiling Main             ( PointCloud.hs, PointCloud.o )
Linking PointCloud ...

> ./PointCloud 
(1,2)
(<calc (1,2)>)
Just 1.0
(1,2)
Just 1.0
(1,5)
(<calc (1,5)>)
Just 1.0
(1,2)
Just 1.0
ghc——生成PointCloud.hs [1/1]编译Main(PointCloud.hs、PointCloud.o) 正在链接点云。。。 >点云 (1,2) () 只有1.0 (1,2) 只有1.0 (1,5) () 只有1.0 (1,2) 只有1.0
如果您想要可变状态,您可以拥有它。只需不断传递更新后的地图,或者将其保存在状态monad中(结果是一样的)

导入符合条件的数据。映射为映射
进口管制站
导入数据.STRef
备忘录:Ord k=>(k->ST s a)->ST s(k->ST s a)
记忆f=do
mc do
c返回a
Nothing->do a>返回a
你可以这样使用它。(实际上,您可能还需要添加一种从缓存中清除项目的方法。)

import-Control.Monad
main::IO()
main=do
fib备忘录化$\n->
如果n<2,则返回n else liftM2(+)(fib(n-1))(fib(n-2))
地图(打印(k->a)->k->a
unsafeMemoize f=unsafePerformIO$do
f'
还有其他选择吗


对纯功能字典的可变引用,如
Data.Map

问题是我无法使用(或者我不知道如何使用)使用不可变类型。我正在尝试构建一个“缓存”函数,但到目前为止,不同的解决方案都非常糟糕。我尝试了这里概述的哈希表方法:我不知道如何使用不可变数据结构编写相同的方法。@ondra:我尝试在我的答案中添加一些指导原则,但这确实有助于了解更多关于pr的信息问题。我看到了你的另一个问题,使用浮点键进行记忆可能会非常痛苦。如果你能更多地讲述问题的大背景,你可能会得到更多有用的帮助。我仍在尝试解决同样的问题。我已经修改了这个问题,以便我知道每个“对象”都有一个唯一的Int32作为函数的参数在上。这使我能够合理有效地缓存东西,但我不确定它是否允许我在Ints上使用记忆。我试图解决的问题是:在球体上工作的优化问题。我在计算点之间的距离-我可以“懒洋洋地计算”一些测角运算,但不是全部。只有大约5%的计算是唯一的,所以如果我可以缓存点之间的距离,我可以节省很多时间。@ondra:你能负担得起对球体进行离散化,引入一些量化吗?如果是这样,请编写代码,以整个解作为答案,然后找到一种方法来懒散地计算它。还有,一个问题:你只做球体的表面还是内部嗯?我有一个模糊的想法,你可以用一个余弦和两个乘法运算的代价来做这件事,这与在大缓存中追逐指针相比是一个巨大的变化……我只需要一个曲面。这可以用一个cos()和一个acos()来计算(所有其他4个必要的sin/cos运算都可以懒散地计算).到目前为止,为了避免这样做,我付出了很多代价。完美!+1,注意函数类型
Map.Map kv->(r,Map.Map kv)
相当于状态monad!类型
MonadState(Map.Map kv)r
来自
控件.Monad.State
。我正在使用一个优化函数,该函数正在处理它的树
import qualified Data.Map as Map
import Control.Monad.ST
import Data.STRef
memoize :: Ord k => (k -> ST s a) -> ST s (k -> ST s a)
memoize f = do
    mc <- newSTRef Map.empty
    return $ \k -> do
        c <- readSTRef mc
        case Map.lookup k c of
            Just a -> return a
            Nothing -> do a <- f k
                          writeSTRef mc (Map.insert k a c) >> return a
import Control.Monad
main :: IO ()
main = do
    fib <- stToIO $ fixST $ \fib -> memoize $ \n ->
        if n < 2 then return n else liftM2 (+) (fib (n-1)) (fib (n-2))
    mapM_ (print <=< stToIO . fib) [1..10000]
import System.IO.Unsafe
unsafeMemoize :: Ord k => (k -> a) -> k -> a
unsafeMemoize f = unsafePerformIO $ do
    f' <- stToIO $ memoize $ return . f
    return $ unsafePerformIO . stToIO . f'

fib :: Integer -> Integer
fib = unsafeMemoize $ \n -> if n < 2 then n else fib (n-1) + fib (n-2)

main :: IO ()
main = mapM_ (print . fib) [1..1000]