Ответ 1
Небольшое отклонение от известного целого три кажется здесь применимым.
{-# 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 [email protected](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
, так как в противном случае мы пропустим места в trie или даже получим non-term, например, в check (falsify t (repeat 0)) 1
.
Временные сложности немного сложнее лень. В check (falsify t is) index
мы платим дополнительную стоимость постоянного количества сравнений log 2 index
и еще одно количество сравнений length (filter (<index) is)
(то есть стоимость перехода по всем индексам, меньшим, чем мы видим). Вы могли бы сказать это O(max(log(index), length(filter (<index) is))
. В любом случае, это определенно лучше, чем O(length is * log (index))
, которое мы получили бы для falsify
, реализованного для конечных is
-es, используя modify
.
Мы должны иметь в виду, что узлы дерева оцениваются один раз, а последующие check
-s для одного и того же индекса после первого check
не платят никакой дополнительной стоимости за falsify
. Опять же, ленивость делает это немного сложнее.
Этот falsify
также очень хорошо себя ведет, когда мы хотим пересечь префикс trie. Возьмите эту функцию 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 (<n) is)
дополнительные сравнения, которые не превосходят 2 * n
, предполагая строго возрастающее is
.
(боковое примечание: потребность в пространстве для прохождения по ширине очень болезненна, но я не вижу простого способа помочь ей, так как итерационное углубление здесь еще хуже, потому что все дерево должно храниться в памяти, тогда как bfs должен помнить только нижний уровень дерева).