Haskell中隐藏状态 - Lazyset

时间:2017-07-18 09:15:03

标签: haskell

我正在尝试在Haskell中构建一个数据结构,允许在有序的无限列表中进行高效查找。

如果这是Java,我会做这样的事情:

class LazySet<T> {

    private Iterator<T> source;
    private NavigableSet<T> set;

    public LazySet(Iterator<T> source){
         this.source = source;
         this.set = new TreeSet<T>() ;               
    }

    public boolean contains(T elem){
        // Fetch items from the source into the set until the element(or one greater than it)
        // has been found
        while (this.set.ceiling(elem) == null){
                if (this.source.hasNext()){
                    this.set.add(this.source.next());
                }else{
                    return false;
                }
        }
        return this.set.contains(elem);
    }    
}

现在虽然这个类显然具有状态,但该状态纯粹是为了优化而不影响类的用户。所以它可以以功能的方式使用。

这个类的Haskell等价物是有状态的。

可能是这样的:

type LazySet a = (Set a, [a])

member :: Ord a => LazySet a -> a -> (Bool, LazySet a)

这将迫使用户显式传递LazySet,这使得它更难使用。

有没有办法告诉haskell:是的,这个东西有状态,但是把它看作是不是吗?

3 个答案:

答案 0 :(得分:4)

不。如果您的函数是有状态的,Haskell 强制您在类型中声明它。有很多方法,包括StateSTIO,但您必须使用其中一种。

虽然起初看起来似乎有限制,但我认为这最终是一件好事。当您知道类型不存在时,它可以更容易地信任库代码。

答案 1 :(得分:1)

看起来像XY问题。把你的来源通过

import Data.List ( unfoldr )
import Control.Arrow ( (***) )
import qualified Data.List.Ordered as O

  (\ chunks -> [ (head chunk, to_balanced_tree nlevels chunk) 
                 | (nlevels, chunk) <- chunks] )
  . unfoldr (\ (xs,n) -> case xs of [] -> Nothing;
        _ -> Just ( (,) n *** flip (,) (n+1) $ splitAt (2^n-1) xs)
  . flip (,) 2
  . O.nub

并在其结果中进行查找 - 树的惰性列表,每个树比前一个树更深,因此包含大约两倍的元素,这应该使查找对数整体。

每个这样构造的树都捆绑了它的最小(最左边)元素。从增加的预期元素列表和给定深度(事先已知)构建平衡树(形式为data Tree a = Leaf | Node (Tree a) a (Tree a))是非常标准的。

由于Haskell的懒惰,对此列表的查找只会强制确定成员资格所需的源(或多或少)。

由于您的源已经被排序(非递减),O.nub会在每个输入元素上花费 O(1)时间,删除所有重复项并在其唤醒中只留下一个唯一元素。您确实表示您的来源是已订购,并且您contains的代码应该是非递减的,否则该代码就是错误的。

Data.List.Ordered来自data-ordlist包。

答案 2 :(得分:1)

此答案假定输入列表已经排序。如果你知道它是按升序排列的,那么问题实际上很容易,正如Will Ness所解释的那样。

你可以用备忘录来做到这一点。基本上诀窍是用静态结构替换mutable- 结构集合类型,然后可以简单地利用Haskell的本机惰性。关于静态结构的重要一点是要有一系列更大的子类。

import Data.List (partition)

data PTrie a = SPTrie a :∧∧: PTrie a
data SPTrie a = NIT | Leaf a | SPTrie a :∧: SPTrie a

fromList :: [(Int, a)] -> PTrie a
fromList = go 0 1
 where go i₀ nC l = chunk :∧∧: go (i₀+nC) (nC*2) rest
        where (chunkSrc, rest) = partition ((<i₀+nC) . fst) l
              chunk = goS i₀ nC chunkSrc
       goS _ _ [] = NIT
       goS _ 1 ((_,k):_) = Leaf k
       goS i₀ nC l = goS i₀ nC' lChunk :∧: goS (i₀+nC') nC' rChunk
        where nC' = nC`quot`2
              (lChunk, rChunk) = partition ((<i₀+nC') . fst) l

llookup :: Int -> PTrie a -> Maybe a
llookup = go 1
 where go nC i (chunk :∧∧: rest)
        | i < nC     = goS nC i chunk
        | otherwise  = go (nC*2) (i-nC) rest
       goS _ _ NIT = Nothing
       goS _ _ (Leaf a) = Just a
       goS nC i (lChunk:∧:rChunk)
        | i<nC'      = goS nC' i lChunk
        | otherwise  = goS nC' (i-nC') rChunk
        where nC' = nC`quot`2