为什么Haskell的Data.Set不支持无限集?

时间:2011-08-19 15:01:07

标签: haskell set

在以下代码段中:

import qualified Data.Set as Set

data Nat = Zero | Succ Nat deriving (Eq, Show, Ord)

instance Enum Nat where
  pred (Succ x)     = x
  succ x            = Succ x
  toEnum 0          = Zero
  toEnum x          = Succ (toEnum (x-1))
  fromEnum Zero     = 0
  fromEnum (Succ x) = 1 + (fromEnum x)

nats :: [Nat]
nats = [Zero ..]

natSet :: Set.Set Nat
natSet = Set.fromList nats

为什么:

  • elem (toEnum 100) nats == True

  • Set.member (toEnum 100) natSet永远不会结束?

5 个答案:

答案 0 :(得分:15)

现有答案已经足够,但我想对Set s的行为进行一点阐述。

看起来你希望所有Nat的懒惰集合;你获取所有Nat的无限列表并在其上使用Set.toList。那样就好了;数学家经常谈论“所有自然数的集合”。问题是Set的实现并不像列表那样适应懒惰。

  

Set的实现基于大小平衡的二叉树(或   有界平衡的树木)

     

The docs for Data.Set

假设您希望从列表中懒惰地构造二叉树。当需要对树进行更深的遍历时,列表中的元素将仅插入到树中。那么你问一下100是否在树上。它会在树上添加1-99号,一次一个。然后它最终将100添加到树中,并发现100确实是树中的元素。但请注意我们做了什么。我们刚刚执行了懒惰列表的有序遍历!所以第一次,我们的虚构LazyTree.contains将具有与List.find大致相同的复杂度(假设一个转移的 O(1)插入,这对于简单二进制文件来说是一个错误的假设树,其 O(log n)复杂度。如果没有平衡,我们的树就会非常不平衡(我们按顺序添加数字1到100,所以它只是每个分支的正确子项下的一个大链表)。但是在遍历期间使用树平衡,很难知道再次开始遍历的位置;至少它肯定不会立即直观。

tl;博士:没人(afaik)已经做了一个很好的懒人套装。因此,无限集合现在更容易表示为无限列表。

答案 1 :(得分:8)

Set.fromList不是懒惰的,所以如果你把它传递给无限列表它就不会结束。但是natSet在需要之前不会被构建,所以当你在它上面运行Set.member时,你才会注意到它。

例如,即使Set.null $ Set.fromList [0..]也不会终止。

答案 2 :(得分:4)

你不能拥有无限集。这不仅会影响Set.member,每当您执行任何会导致natSet进行评估甚至一步(甚至Set.null)的任何事情时,它都将进入无限循环。

答案 3 :(得分:1)

让我们看看当我们调整GHC's Set code以适应无限集时会发生什么:

module InfSet where

data InfSet a = Bin a (InfSet a) (InfSet a) 

-- create an infinite set by unfolding a value
ofUnfold :: (x -> (x, a, x)) -> x -> InfSet a
ofUnfold f x = 
  let (lx,a,rx) = f x
      l = ofUnfold f lx
      r = ofUnfold f rx
  in Bin a l r

-- check for membership in the infinite set
member :: Ord a => a -> InfSet a -> Bool
member x (Bin y l r) = case compare x y of
                         LT -> member x l
                         GT -> member x r
                         EQ -> True       

-- construct an infinite set representing a range of numbers
range :: Fractional a => (a, a) -> InfSet a
range = ofUnfold $ \(lo,hi) -> 
          let mid = (hi+lo) / 2
          in ( (lo, mid), mid, (mid, hi) )

请注意,不是从无限列表构造无限集, 我改为定义一个函数ofUnfold来将单个值展开到无限列表中。 它允许我们平行地构建两个分支(我们不需要完成 在构建另一个分支之前的一个分支。)

让我们给它一个旋转:

ghci> :l InfSet
[1 of 1] Compiling InfSet           ( InfSet.hs, interpreted )
Ok, modules loaded: InfSet.
ghci> let r = range (0,128)
ghci> member 64 r
True
ghci> member 63 r
True
ghci> member 62 r
True
ghci> member (1/2) r
True
ghci> member (3/4) r
True

嗯,这似乎有效。如果我们在Set之外尝试一个值会怎么样?

ghci> member 129 r
^CInterrupted.

这将只是运行并运行,永不退出。在无限集中没有停止分支, 所以搜索永远不会退出。我们可以以某种方式检查原始范围,但这对于无限的离散元素集是不实用的:

ghci> let ex = ofUnfold (\f -> ( f . (LT:), f [EQ], f . (GT:) )) id
ghci> :t ex
ex :: InfSet [Ordering]
ghci> member [EQ] ex
True
ghci> member [LT,EQ] ex
True
ghci> member [EQ,LT] ex
^CInterrupted.

所以无限集可能但我不确定它们是有用的

答案 4 :(得分:0)

我有同样的感觉,所以我添加了一个与无限列表一起使用的集合。但是它们需要进行排序,因此我的算法知道何时停止寻找更多。

Prelude> import Data.Set.Lazy
Prelude Data.Set.Lazy> natset = fromList [1..]
Prelude Data.Set.Lazy> 100 `member` natset
True
Prelude Data.Set.Lazy> (-10) `member` natset
False

它的hackage。 http://hackage.haskell.org/package/lazyset-0.1.0.0/docs/Data-Set-Lazy.html