Haskell 基于共享元素递归合并列表列表
我不知道我要做的事情的官方技术名称是什么,所以我会尽力解释 给出一个列表:Haskell 基于共享元素递归合并列表列表,haskell,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]] 我遇到的问题是在每个元素之间运行匹配过程。基本上,这就像旧的数学课堂题,房间里的每个人都必须与其他人握手。通常,我会使
[[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的递归来实现呢
任何帮助都将是伟大的 如果有有限数量的不同元素,您可以将任务从内到外,并从您的
[[elem]]]
映射元素[[elem]],然后通过下一个算法开始迭代合并元素:
如果有有限数量的不同元素,您可以将任务从内到外,从您的
[[elem]]
中生成一个Ord elem=>Map elem[[elem]]
,然后通过下一个算法开始迭代合并元素:
*.lhs
,并将其加载到GHCi中。还要注意,所讨论的算法具有运行时O(n²),并且不是最优的。更好的方法是使用或类似的方法
首先,如果要将单个列表x
与其余列表xs
分组,让我们考虑一下所需的工具。我们需要从xs
列表中分离出与x
有共同元素的列表,并需要构建此类列表的联合。因此,我们应该从数据中导入一些函数。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 [] = []
假设我们至少有两个列表。我们取第一个,检查它们是否有与任何其他列表相同的元素。我们可以使用分区:
> simUnion (x:xs) =
> let (common, noncommon) = partition (intersects x) xs
现在,common::[[a]]
将只包含至少有一个公共元素的列表。现在可以有两种情况:要么common
为空,要么我们的列表x
与xs
中的任何列表没有共同元素:
> in if null common
> then x : simUnion xs
我们在这里忽略了不常见的
,因为在这种情况下xs==不常见的
。在另一种情况下,我们需要在common
和x
中构建所有列表的并集。这可以通过foldr union
完成。但是,此新列表必须再次在simUnion
中使用,因为它可能有新的交点。例如,在
simUnion [[1,2], [2,3], [3,4]]
您希望以[[1,2,3,4]]
结束,而不是[[1,2,3],[3,4]
:
> else simUnion (foldr union x common : noncommon)
请注意,结果将被取消排序,但作为最后一步,您可以映射排序。注意:下面的帖子是用识字的Haskell写的。将其另存为*.lhs
,并将其加载到GHCi中。还要注意,所讨论的算法具有运行时O(n²),并且不是最优的。更好的方法是使用或类似的方法
首先,如果要将单个列表x
与其余列表xs
分组,让我们考虑一下所需的工具。我们需要从xs
列表中分离出与x
有共同元素的列表,并需要构建此类列表的联合。因此,我们应该从数据中导入一些函数。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 [] = []
假设我们至少有两个列表。我们取第一个,检查它们是否有与任何其他列表相同的元素。我们可以使用分区:
> simUnion (x:xs) =
> let (common, noncommon) = partition (intersects x) xs
现在,common::[[a]]
将只包含至少有一个公共元素的列表。现在可以有两种情况:要么common
为空,要么我们的列表x
与xs
中的任何列表没有共同元素:
> in if null common
> then x : simUnion xs
我们在这里忽略了不常见的
,因为在这种情况下xs==不常见的
。在另一种情况下,我们需要在common
和x
中构建所有列表的并集。这可以通过foldr union
完成。但是,此新列表必须再次在simUnion
中使用,因为它可能有新的交点。例如,在
simUnion [[1,2], [2,3], [3,4]]
您希望以[[1,2,3,4]]
结束,而不是[[1,2,3],[3,4]
:
> else simUnion (foldr union x common : noncommon)
请注意,结果将被取消排序,但作为最后一步,您可以映射排序。我有两个主要建议:
不要把它看成是递归!相反,自由地使用库实用程序函数
使用适当的数据结构!因为您正在谈论成员资格测试和联合,所以集合(来自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]]
通常我会