在Haskell中更新另外两个集合时迭代两个集合

时间:2013-03-02 22:29:45

标签: haskell nested-loops set

我正在尝试将以下命令式代码转换为Haskell中的功能性解决方案。我想比较集s的成员与集s'的成员,并根据比较更新集tt'。这是命令式伪代码:

-- s, s', t, t' are of type Data.Set a
-- foo x y returns a Just a or Nothing 
foo :: a -> a -> Just a

-- Initialize t and t' to s and s'
t = s
t = s'

foreach x in s
  foreach y in s'
      if (foo x y) == Just z
        insert z into t
        delete x from t
        delete y from t'

return (t, t')

我希望的Haskell函数的类型可能是,

mergeSets :: S.Set -> S.Set -> (S.Set, S.Set)
mergeSets s s' = ...

其中S的类型为Data.Set,函数的结果将与新集合tt'成对(或以其他方式返回两个集合tt')。

2 个答案:

答案 0 :(得分:4)

这是一种可能性:

bar s s' =
  foldl (\ (t,t') (z,(x,y)) -> 
              ( delete x (insert z t) , delete y t' ))
    (s,s')
    [(z,(x,y)) | x <- toList s, y <- toList s', Just z <- [foo x y]]

这里的主要问题是您是否打算进行插入和删除以干扰foreach机制。以上假设你没有。

如果你的套装很大,你可能需要增加严格性,以避免暴击:

bar s s' = foldl (\ (t,t') (z,(x,y)) ->
             let a=insert z t; b=delete x a; c=delete y t' 
             in a `seq` b `seq` c `seq` (b,c) )
    ....

答案 1 :(得分:2)

如果您正在使用像Set这样的集合数据类型,那么通常不会在元素上编写循环。这对列表更合适。因此,如果您的算法需要以某种嵌套方式枚举所有元素,请将Set转换为a list.

因此,我会尽量避免在集合上使用嵌套循环算法,而是在集合操作方面寻找一些声明性规范:交集,并集,差异等。

如果那是不可能的,那么列表的天真翻译肯定是可能的:

import qualified Data.Set as S

mergeSets :: Ord a => S.Set a -> S.Set a -> (S.Set a, S.Set a)
mergeSets s t = go1 (S.toList s) s t
    where
        go1 []     t t' = (t,t')
        go1 (x:xs) t t' = go1 xs u u'
            where
                (u, u') = go2 x (S.toList t) t t'

        go2 x []     t t'       = (t,t')
        go2 x (y:ys) t t'
            | Just z <- foo x y = go2 x ys (S.delete x (S.insert z t)) (S.delete y t')
            | otherwise         = go2 x ys t t'

-- for example
foo x y = if x == y then Just x else Nothing

简单的一点就是对嵌套循环进行建模。我们可以使用例如列表理解。但是,您的算法使用输出集的变异,因此我们需要将其作为累积参数传递。

为了真正很好地融入Haskell,我们需要放弃逐个元素的命令式风格,并按照自然的Set api工作。这将是您通往一个不错的解决方案的途径。