【问题标题】:Does a useful Haskell HashMap/HashTable/Dictionary library exist?是否存在有用的 Haskell HashMap/HashTable/Dictionary 库?
【发布时间】:2017-01-07 10:00:52
【问题描述】:

我正在寻找一个无 monad 的常量访问查询 O(1) 关联数组。

考虑假设的类型:

data HT k v = ???

我想构造一个不可变结构一次:

fromList :: Foldable t, Hashable k => t (k,v) -> HT k v

我想随后用恒定时间访问重复查询它::

lookup :: Hashable k => HT k v -> k -> Maybe v

似乎有两个候选库不足:

unordered-containers

unordered-containers 包含HashMap 类型的严格变体和惰性变体。 HashMaps 都有 O(log n) 查询,如 lookup 函数所记录。这个查询访问时间似乎是由于HashMap 类型的构造,它具有允许O(log n) insert 功能的内部树结构。对于许多用例来说,这是一种可以理解的设计权衡,但由于我不需要可变的HashMap,这种权衡阻碍了我的用例。

hashtables

hashtables 包含一个HashTable 类型类和三个具有不同表构造策略的实例类型。这个库的类型类定义了一个常数时间 O(1) lookup 函数定义,但它永远嵌入在 ST monad 中。没有办法“冻结”有状态的 HashTable 实现并拥有一个未嵌入有状态 monad 的 lookup 函数。当整个计算包装在一个状态单子中时,库的类型类接口设计得很好,但是这种设计不适合我的用例。


是否存在其他一些定义类型和函数的库,这些库可以构造不可变的常量访问查询O(1) 关联数组,该数组未嵌入有状态的 monad?

是否存在某种方法来包装或修改这些现有的基于散列的库以生成不嵌入在有状态 monad 中的不可变常量访问查询 O(1) 关联数组?

【问题讨论】:

  • 首先:我相信你是对的。没有纯 + O(1) 查找键值结构。 OTOH,出现的第一个问题是必要的。避免使用 monad(包括避免使用 IO)是一种通常缺乏技术依据的美学选择——在这种情况下,避免使用显然要付出代价。同样,已经提出过早优化的怀疑。如果您愿意,请考虑为hashtables 库添加一个补丁,该补丁添加了冻结操作和纯查找。或者试试 HashMap 看看具体性能如何。
  • @ThomasM.DuBuisson 我在 O(1) 查询中使用了(i,j) 键矩阵,但是矩阵的维度增长得很快,而且矩阵稀疏填充上三角矩阵。内存要求不可扩展。当我找不到合适的哈希表结构来替换查找矩阵时,我感到非常震惊。有一个 feature request 用于冻结 HashTable,但没有得到答复,我目前无法构建拉取请求。

标签: haskell hashmap hashtable asymptotic-complexity hackage


【解决方案1】:

是否存在其他一些定义类型和函数的库,这些库可以构造不嵌入在有状态单子中的不可变常量访问查询 O(1) 关联数组?

在这个时间点上,答案仍然是否定的。

截至 2019 年底,有一个基于 IO 的高效 hashtable 软件包,具有不错的基准。

你所描述的似乎是可行的,就像纯粹的、不可变的Data.Array 构造是可能的一样。请参阅 Data.Array.Base 了解如何通过 unsafe* 运算符实现此目的。 Data.Array 定义了一个边界,我最初的想法是,如果允许一个纯的、不可变的哈希表无边界地增长,它可能会出现 GC 问题。

【讨论】:

    【解决方案2】:

    您应该听从 Alexis 的建议并使用 unordered-containers。如果你真的想要保证有Θ(1)lookups 的东西,你可以使用unsafePerformIOhashtables 定义你自己的任何散列表类型的冻结版本,但这不是很优雅。例如:

    module HT
        ( HT
        , fromList
        , lookup
        ) where
    
    import qualified Data.HashTable.IO as H
    import Data.Hashable (Hashable)
    import Data.Foldable (toList)
    import System.IO.Unsafe (unsafePerformIO)
    import Prelude hiding (lookup)
    
    newtype HT k v = HT (H.BasicHashTable k v)
    
    fromList :: (Foldable t, Eq k, Hashable k) => t (k, v) -> HT k v
    fromList = HT . unsafePerformIO . H.fromList . toList
    
    lookup :: (Eq k, Hashable k) => HT k v -> k -> Maybe v
    lookup (HT h) k = unsafePerformIO $ H.lookup h k
    

    以上unsafePerformIO 的两种用法都应该是安全的。因为HT 导出为抽象类型至关重要。

    【讨论】:

    • 你可以使用runHT :: (forall s. ST s (HashTable s k v)) -> HT k v,而不是fromList
    【解决方案3】:

    你想要的图书馆是……unordered-containers。或者只是简单的旧 Data.Map 来自 containers,如果你愿意的话。

    unordered-containers documentation 中的注释解释了为什么您不必担心查找的 O(log n) 时间复杂度:

    许多操作的平均情况复杂度为 O(log n)。该实现使用较大的基数(即 16),因此在实践中这些操作是常数时间。

    这是某些函数数据结构的常见做法,因为它允许良好的共享属性,同时也具有良好的时间复杂性。即使对于非常大的 n,log16 仍然会产生 非常小的 数,因此您几乎总是可以将这些复杂性视为“有效的恒定时间”。

    如果这曾经是您的应用程序的瓶颈,当然可以使用其他方法,但我认为这不太可能。毕竟,log16(1,000,000) 略低于 5,因此您的查找时间不会很快增长。处理所有这些数据将花费比查找开销更多的时间。

    一如既往:个人资料优先。如果您有一个绝对需要世界上最快的哈希映射的问题,您可能需要一个命令式哈希映射,但对于我曾经遇到的每种情况,功能性的都可以正常工作。

    【讨论】:

    • 我没有阅读 unordered-containersHashMap 类型的标头文档,只阅读了 lookup 函数的文档,所以我错过了您引用的有关大型日志库的信息。我将尝试使用HashMap 类型并分析性能。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 2012-06-09
    • 1970-01-01
    • 2010-11-15
    • 2012-04-27
    • 2017-07-22
    • 1970-01-01
    相关资源
    最近更新 更多