我不知道我正在尝试做什么的官方技术名称,所以我会尽力解释它。
给出列表清单:
[[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的递归来做呢?
任何帮助都会很棒!
答案 0 :(得分:1)
如果存在有限数量的不同元素,则可以将任务内部转出并从Ord elem => Map elem [[elem]]
中生成[[elem]]
,然后通过下一个算法开始迭代合并元素:
答案 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
为空,我们的列表x
与xs
中的任何列表都没有任何共同点:
> in if null common
> then x : simUnion xs
我们在此忽略uncommon
,因为在这种情况下xs == uncommon
。在另一种情况下,我们需要在common
和x
中构建所有列表的并集。这可以使用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)
我有两个主要建议:
Data.Set
模块)听起来就像是一个更好的选择。应用这些想法,这是一个相当简单(尽管可能非常天真和次优)的解决方案:
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.map
和unionMap
。请注意,这些函数类似于循环,我们以嵌套方式使用它们。经验法则:循环的命令通常转化为功能映射,过滤,减少或类似操作。嵌套的命令式循环相应地经常转换为嵌套使用这些函数。