最有效的方法来创建Set中所有元素对的Data.Set?

时间:2011-11-21 13:03:55

标签: performance haskell

给定一个包含任意数量的任意类型元素的任意集合,例如

mySet1 = Set.fromList [1,2,3,4]

mySet2 = Set.fromList ["a","b","c","d"]

mySet3 = Set.fromList [A, B, C, D]

对于某些数据构造函数A,B,C,D,...

生成所有无序元素对的计算最有效的方法是给定集合?即。

setPairs mySet1 == Set.fromList [(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]

setPairs mySet2 == fromList [ ("a","b")
                            , ("a","c")
                            , ("a","d")
                            , ("b","c")
                            , ("b","d")
                            , ("c","d") ]

setPairs mySet2 == fromList [ (A,B)
                            , (A,C)
                            , (A,D)
                            , (B,C)
                            , (B,D)
                            , (C,D) ]

我最初的天真猜测是:

setPairs s = fst $ Set.fold
    (\e (pairAcc, elementsLeft) ->
        ( Set.fold
              (\e2 pairAcc2 ->
                  Set.insert (e2, e) pairAcc2
              ) pairAcc $ Set.delete e elementsLeft
        , Set.delete e elementsLeft )
    ) (Set.empty, s) s

但肯定不是最好的解决方案吗?

3 个答案:

答案 0 :(得分:6)

基准测试可能证明我错了,但我的怀疑是,在设定的代表中没有胜利。无论如何你都需要O(n ^ 2),因为这是输出的大小。关键优势在于生成列表,以便您可以使用S.fromDistinctAscList调用,这样只需要花费O(n)来构建集合。

以下内容非常简洁,保留了相当多的共享,并且通常是我能想象到的最简单,最直接和最直观的解决方案。

pairs s = S.fromDistinctAscList . concat $ zipWith zip (map (cycle . take 1) ts) (drop 1 ts)
   where ts = tails $ S.toList s

修改

更短/更清晰(不确定性能,但可能更好/更好):

pairs s = S.fromDistinctAscList [(x,y) | (x:xt) <- tails (S.toList s), y <- xt]

答案 1 :(得分:1)

首先,您需要生成所有集合。来自Control.Monad的replicateM有助于它。

λ> replicateM 2 [1..4]
[[1,1],[1,2],[1,3],[1,4],[2,1],[2,2],[2,3],[2,4],[3,1],[3,2],[3,3],[3,4],[4,1],[4,2],[4,3],[4,4]]

然后你需要过滤对,其中第二个元素大于第一个

λ> filter (\[x,y] -> x < y)  $ replicateM 2 [1 .. 4]
[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]

最后,您需要转换元组中的每个列表

λ> map (\[x,y] -> (x,y)) $ filter (\[x,y] -> x < y)  $ replicateM 2 [1 .. 4]
[(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]

然后我们可以将其表达为函数pairs

import Data.Set
import Control.Monad
import Data.List

mySet = Data.Set.fromList [1,2,3,4]

--setOfPairs = Data.Set.fromList [(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]
setOfPairs = Data.Set.fromList $ pairs mySet

pairs :: Ord a => Set a -> [(a,a)]
pairs x = Data.List.map (\[x,y] -> (x,y)) $ Data.List.filter (\[x,y] -> x < y) $ replicateM 2 $ toList x

所以,如果我对你提出了问题,你可以使用pairs mySet,其中对生成所有无序对mySet的列表。

这是你想要的吗?

<强> UPD

List comprehension可以更清晰,更快速地创建此类子列表,因此这里是pairs的另一个实例:

pairs :: Ord a => Set a -> [(a,a)]
pairs set = [(x,y) | let list = toList set, x <- list, y <- list, x < y]

答案 2 :(得分:1)

所以这是第一次尝试使用来回转换到列表的解决方案。同样,我不确定这是做到这一点的最快方法,但我确实知道迭代设置它不是非常有效。

import Data.List
import qualified Data.Set as S

pairs :: S.Set String -> S.Set (String,String)
pairs s = S.fromList $ foldl' (\st e -> (zip l e) ++ st) [] ls
          where (l:ls) = tails $ S.toList s

通过在尾部折叠拉链,您可以获得一种非常有效的方法来创建无序对。然而,本能鼓励我可能有一个更优雅的monadic filterM或foldM解决方案。我会继续思考。

[编辑]

所以这应该是[但不是因为powerset的大小]更快的解决方案,不需要toList。

import Data.List
import qualified Data.Set as S
import qualified Data.Foldable as F

pairs :: (Ord a) => S.Set a -> S.Set (a,a)
pairs s = S.fromList $ foldl two [] $ F.foldlM (\st e -> [[e]++st,st]) [] s
          where two st (x:xa:[]) = (x,xa) : st
                two st _ = st

使用monadic列表上的power-set解决方案来构建powerset,然后过滤掉这些对。如有必要,我可以详细介绍。