我正在尝试在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:是的,这个东西有状态,但是把它看作是不是吗?
答案 0 :(得分:4)
不。如果您的函数是有状态的,Haskell 强制您在类型中声明它。有很多方法,包括State
,ST
和IO
,但您必须使用其中一种。
虽然起初看起来似乎有限制,但我认为这最终是一件好事。当您知道类型不存在时,它可以更容易地信任库代码。
答案 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