【问题标题】:Infinite lazy bitmap无限惰性位图
【发布时间】:2014-09-02 11:04:44
【问题描述】:

我正在尝试构建一个包含无限位图的惰性数据结构。我愿意支持以下操作:

  1. true :: InfBitMap

    返回 True 的无限位图,即所有位置的值都应为 True。

  2. falsify :: InfBitMap -> [Int] -> InfBitMap

    将列表中的所有位置设置为 False。该列表可能是无限的。例如,falsify true [0,2..] 将返回一个列表,其中所有(且只有)奇数位置为 True。

  3. check :: InfBitMap -> Int -> Bool

    检查索引的值。

到目前为止,这是我能做的。

-- InfBitMap will look like [(@), (@, @), (@, @, @, @)..]
type InfBitMap = [Seq Bool]

true :: InfBitMap
true = iterate (\x -> x >< x) $ singleton True

-- O(L * log N) where N is the biggest index in the list checked for later
-- and L is the length of the index list. It is assumed that the list is
-- sorted and unique.
falsify :: InfBitMap -> [Int] -> InfBitMap
falsify ls is = map (falsify' is) ls
     where
         -- Update each sequence with all indices within its length
         -- Basically composes a list of (update pos False) for all positions
         -- within the length of the sequence and then applies it.
         falsify' is l = foldl' (.) id
                                (map ((flip update) False)
                                     (takeWhile (< length l) is))
                         $ l
-- O(log N) where N is the index.
check :: InfBitMap -> Int -> Bool
check ls i = index (fromJust $ find ((> i) . length) ls) i

我想知道是否有一些我缺少的 Haskellish 概念/数据结构可以使我的代码更优雅/更高效(常量对我来说无关紧要,只需订购)。我尝试查看拉链和镜片,但它们似乎没有帮助。我想保持更新和检查的复杂性对数(可能只是摊销对数)。

注意:在有人怀疑之前,不,这不是作业问题!

更新

我突然想到检查可以改进为:

-- O(log N) where N is the index.
-- Returns "collapsed" bitmap for later more efficient checks.
check :: InfBitMap -> Int -> (Bool, InfBitMap)
check ls i = (index l i, ls')
    where
        ls'@(l:_) = dropWhile ((<= i) . length) ls

可以将其转换为 Monad 以保持代码整洁。

【问题讨论】:

  • 你确定InfBitMap?如果true 应该与repeat True 同构,则InfBitMap 需要是[Bool]Seq Bool,但不能同时是两者。
  • @Zeta 我的意思并不是说真要同构重复真。我的意思是它拥有无限的 True 序列。如果重复 True 是同构的,那将使检查在 N 中成为线性。
  • falsify 的复杂性似乎不正确。 L 更新了 N 长度序列,已经是 O(L * log N)。
  • @AndrásKovács 在第一个序列中最多有 1 次更新,在第二个中最多有 2 次,...,在第 k 个中最多有 2^k 次更新。总结一下,更新次数
  • @aelguindy for Data.Sequence 单个更新的大小为日志 N。我不太明白“L + log N”中的“+”是从哪里来的。

标签: haskell data-structures


【解决方案1】:

著名的integer trie 的细微变化似乎适用于此。

{-# LANGUAGE DeriveFunctor #-}

data Trie a = Trie a (Trie a) (Trie a) deriving (Functor)

true :: Trie Bool
true = Trie True true true

-- O(log(index))
check :: Trie a -> Int -> a
check t i | i < 0 = error "negative index"
check t i = go t (i + 1) where
    go (Trie a _ _) 1 = a
    go (Trie _ l r) i = go (if even i then l else r) (div i 2)

--O(log(index))
modify :: Trie a -> Int -> (a -> a) -> Trie a
modify t i f | i < 0 = error "negative index"
modify t i f = go t (i + 1) where
    go (Trie a l r) 1 = Trie (f a) l r
    go (Trie a l r) i | even i = Trie a (go l (div i 2)) r
    go (Trie a l r) i = Trie a l (go r (div i 2))

不幸的是,我们不能使用modify 来实现falsify,因为我们不能以这种方式处理无限的索引列表(所有修改都必须在可以检查trie 的元素之前执行)。相反,我们应该做一些更像是合并的事情:

ascIndexModify :: Trie a -> [(Int, a -> a)] -> Trie a
ascIndexModify t is = go 1 t is where
    go _ t [] = t
    go i t@(Trie a l r) ((i', f):is) = case compare i (i' + 1) of
        LT -> Trie a (go (2*i) l ((i', f):is)) (go (2*i+1) r ((i', f):is))
        GT -> go i t is
        EQ -> Trie (f a) (go (2*i) l is) (go (2*i+1) r is)

falsify :: Trie Bool -> [Int] -> Trie Bool
falsify t is = ascIndexModify t [(i, const False) | i <- is]

我们假设is 中的索引是严格升序的,否则我们会跳过特里树中的位置,甚至会出现非终止,例如check (falsify t (repeat 0)) 1

时间复杂度因懒惰而有些复杂。在check (falsify t is) index 中,我们支付了额外的固定log 2 index 比较次数和更多length (filter (&lt;index) is) 比较次数(即遍历所有小于我们正在查找的索引的成本)。你可以说它是O(max(log(index), length(filter (&lt;index) is))。无论如何,它绝对比使用modify 为有限is-es 实现的falsify 得到的O(length is * log (index)) 更好。

我们必须记住,树节点被评估一次,并且在第一个 check 之后的同一索引的后续 check-s 不会为 falsify 支付任何额外费用。同样,懒惰使这有点复杂。

这个falsify 在我们想要遍历一个树的前缀时也表现得很好。取这个toList函数:

trieToList :: Trie a -> [a]
trieToList t = go [t] where
    go ts = [a | Trie a _ _ <- ts] 
            ++ go (do {Trie _ l r <- ts; [l, r]})

这是一个标准的广度优先遍历,在线性时间内。当我们计算take n $ trieToList (falsify t is) 时,遍历时间保持线性,因为falsify 最多产生n + length (filter (&lt;n) is) 额外比较,最多为2 * n,假设严格增加is

(旁注:广度优先遍历的空间要求相当痛苦,但我找不到简单的方法来帮助它,因为这里的迭代加深更糟糕,因为整个树必须保存在内存中,而 bfs 只需要记住树的底层)。

【讨论】:

    【解决方案2】:

    将其表示为函数的一种方法。

    true = const True
    
    falsify ls is = \i -> not (i `elem` is) && ls i
    
    check ls i = ls i
    

    true 和 falsify 函数很好而且很高效。检查函数可能与线性一样糟糕。提高相同基本思想的效率是可能的。我喜欢它的优雅。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-11
      • 2021-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-20
      • 2016-06-27
      相关资源
      最近更新 更多