基于共享元素递归地合并列表列表

时间:2016-03-09 16:10:15

标签: haskell

我不知道我正在尝试做什么的官方技术名称,所以我会尽力解释它。

给出列表清单:

[[2,3,4,5], [1,5,6], [7,8,9]]

我想只联合具有至少一个共同元素的列表。所以基本上是这样的:

simUnion :: [[Int]] -> [[Int]]
simUnion list = --...

--Result
-- [[1,2,3,4,5,6], [7,8,9]]

我遇到的问题是在每个元素之间运行匹配过程。基本上这就像旧的数学课问题,一个房间里的每个人都必须握住对方的手。通常我会用嵌套的for循环完成这个,但是我怎么能用Haskell的递归来做呢?

任何帮助都会很棒!

3 个答案:

答案 0 :(得分:1)

如果存在有限数量的不同元素,则可以将任务内部转出并从Ord elem => Map elem [[elem]]中生成[[elem]],然后通过下一个算法开始迭代合并元素:

  1. 虽然地图不是空的,拿走钥匙,把它放入队列
  2. 获取包含从队列中弹出的键的所有组
  3. 将它们连成并放入队列(也在某些累加器中)
  4. 如果队列为空,则该组完成;从地图中取出另一把钥匙

答案 1 :(得分:1)

注意:以下帖子是用文字Haskell编写的。将其保存为*.lhs并将其加载到GHCi中。还要注意,所讨论的算法具有运行时O(n²)并且不是最佳的。更好的方法是使用union find或类似的。

首先,如果我们想要将单个列表x与其余列表xs分组,让我们考虑一下我们需要的工具。我们需要将xs中与x具有共同元素的列表分开,我们需要构建此类列表的union。因此,我们应该从Data.List

导入一些函数
> import Data.List (partition, union)

接下来,我们需要检查两个列表是否适合合并:

> intersects :: Eq a => [a] -> [a] -> Bool
> intersects xs ys = any (`elem` ys) xs

现在我们掌握了所有工具来定义simUnion。空案例很清楚:如果我们没有任何列表,结果也没有任何列表:

> simUnion :: Eq a => [[a]] -> [[a]]
> simUnion []     = []

假设我们至少有两个列表。我们拿第一个并检查它们是否与任何其他列表有任何共同点。我们可以使用partition

来实现
> simUnion (x:xs) = 
>   let (common, noncommon) = partition (intersects x) xs

现在,common :: [[a]]将只包含至少有一个共同元素的列表。现在可能有两种情况:common为空,我们的列表xxs中的任何列表都没有任何共同点:

>   in if null common 
>         then x : simUnion xs

我们在此忽略uncommon,因为在这种情况下xs == uncommon。在另一种情况下,我们需要在commonx中构建所有列表的并集。这可以使用foldr union完成。但是,必须再次在simUnion中使用此 new 列表,因为它可能有新的交叉点。例如,在

simUnion [[1,2], [2,3], [3,4]]

您希望最终得到[[1,2,3,4]],而不是[[1,2,3],[3,4]]

>          else simUnion (foldr union x common : noncommon)

请注意,结果将是未排序的,但您可以map sort作为最后一步。

答案 2 :(得分:1)

我有两个主要建议:

  1. 不要在递归方面考虑它!相反,要自由使用库效用函数。
  2. 使用适当的数据结构!既然您正在谈论成员资格测试和工会,那么集合(来自Data.Set模块)听起来就像是一个更好的选择。
  3. 应用这些想法,这是一个相当简单(尽管可能非常天真和次优)的解决方案:

    import Data.Set (Set)
    import qualified Data.Set as Set
    
    simUnion :: Set (Set Int) -> Set (Set Int)
    simUnion sets = Set.map outer sets
      where outer :: Set Int -> Set Int
            outer set = unionMap middle set
                where middle :: Int -> Set Int
                      middle i = unionMap inner sets
                          where inner :: Set Int -> Set Int
                                inner set
                                    | i `Set.member` set = set
                                    | otherwise          = Set.empty
    
    -- | Utility function analogous to the 'concatMap' list function, but
    -- for sets.
    unionMap :: (Ord a, Ord b) => (a -> Set b) -> Set a -> Set b
    unionMap f as = Set.unions (map f (Set.toList as))
    

    现在使用你的例子:

    -- | This evaluates to:
    --
    -- >>> simUnion sampleData
    -- fromList [fromList [1,2,3,4,5,6],fromList [7,8,9]]
    sampleData :: Set (Set Int)
    sampleData = Set.fromList (map Set.fromList sampleData')
        where sampleData' :: [[Int]]
              sampleData' = [[2,3,4,5], [1,5,6], [7,8,9]]
    
      

    通常我会用嵌套的for循环完成这个,但是我怎么能用Haskell的递归来做呢?

    您不直接使用递归。您使用的是高阶函数,例如Set.mapunionMap。请注意,这些函数类似于循环,我们以嵌套方式使用它们。经验法则:循环的命令通常转化为功能映射,过滤,减少或类似操作。嵌套的命令式循环相应地经常转换为嵌套使用这些函数。