我正在处理程序中的列表,我希望能够快速检查两个列表是否相交。我尝试实现的是
commonEle :: (Eq a) => [a] -> [a] -> Bool
commonEle _ [] = False
commonEle [] _ = False
commonEle (x:xs) ys
|x `elem` ys = True
|otherwise = commonEle xs ys
这个有效,但我正在努力提高效率,以免事情发生。 This SO question在其中一个答案中解释说,使用集合来快速检查交叉点会更有效。我的列表将自动具有不同的元素,因此使用集合可能是个好主意,但构建我的数据的自然方式是列表理解,因此我必须使用fromList
函数将其转换为组。您如何判断哪种实施更有效?
对于记录,我将不得不检查许多相当小的列表(大约10 ^ 5的大小< 100)。
答案 0 :(得分:2)
您是要针对单个列表检查所有列表,还是需要测试所有可能的配对?
我正在检查很多非常小的名单。
我问过,因为一个问题是必须用fromList
转换过多的小列表。由于其中一个列表已修复,您只需转换固定列表即可避免大部分成本。
import qualified Data.Set as S
import Data.Set (Set)
-- There is probably a better name for this modified version.
commonEle :: (Ord a) => Set a -> [a] -> Bool
commonEle xs ys = any (`S.member` xs) ys
如果您正在编写益智游戏,您可能会考虑永久保留这部分益智状态为Set
,这样您就不必在每次移动时重新创建该集。 (如果事实证明您需要保留与职位相关的额外信息,那么还有来自Map
/ Data.Map
/ Data.Map.Strict
的{{1}}类型。
无论如何,请遵循Carsten的建议:"最好的方法是尝试(测量它)"。另外,请务必检查计划在相关模块文档中使用的功能的承诺性能特征。
答案 1 :(得分:2)
您在评论中提到,您的所有内容都将成为坐标对(Int,Int)
,其中每个坐标都在[1..10]
范围内。
在这种情况下,您应该使用“位设置”,以便您可以使用处理器的按位AND和OR运算来设置交集和并集。
模块Data.BitSet.Dynamic可用于此目的:
import qualified Data.BitSet.Dynamic as B
type Bset = B.BitSet B.FasterInteger
-- convert a list of points into a bit set
toSet :: [(Int,Int)] -> Bset
toSet points = B.fromList [ fromIntegral (r*10+c) | (r,c) <- points ]
-- do two bit sets have common elements?
commonElements :: Bset -> Bset -> Bool
commonElements a b = not $ B.null $ B.intersection a b
-- add a point to a bit set
addPoint :: Bset -> (Int,Int) -> Bset
addPoint a (r,c) = B.insert (fromIntegral $ r*10+c) a
-- convert a bit set to a list of points
toPoints :: Bset -> [(Int,Int)]
toPoints a = [ (q,r) | x <- B.toList a, let (q,r) = divMod (fromIntegral x) 10 ]
-- does a set have a point?
hasPoint :: Bset -> (Int,Int) -> Bool
hasPoint a (r,c) = B.member (fromIntegral $ r*10+c) a
检测两个集合是否有任何共同元素,或者一个点是否在一个集合中是非常快的。