当groupBy函数根据用户定义的谓词相等时以及当它们在列表中相邻时,对列表中的元素进行分组。有没有将相邻元素分组的功能?
我处理三个变量的多项式。这是写成单项式和的多项式的示例:
p = (M (Monomial {coefficient = 1.0, powers = (1,0,0)}) :+: M (Monomial {coefficient = 1.0, powers = (0,1,0)})) :+: M (Monomial {coefficient = 1.0, powers = (1,0,0)})
这意味着p = 1.0 * x^1y^0z^0 + 1.0 * x^0y^1z^0 + 1.0 * x^1y^0z^0
。第一项和第三项是可加的:它们具有相同的powers
,然后我们可以将它们相加得到2.0 * x^1y^0z^0
。我的目标是进行此添加,以简化p
。
我有一个函数,可以将这样的p
转换为总和的单体物列表:
>> toListOfMonomials p
[M (Monomial {coefficient = 1.0, powers = (1,0,0)}),M (Monomial {coefficient = 1.0, powers = (0,1,0)}),M (Monomial {coefficient = 1.0, powers = (1,0,0)})]
我想对具有相同powers
的单项式分类器进行分组。如果我这样做
groupBy ((==) `on` getPowers)
(toListOfMonomials p)
然后将两个M (Monomial {coefficient = 1.0, powers = (1,0,0)})
分组,因为它们不相邻。
一种解决方案是在使用groupBy
之前根据能力对列表进行排序。这样,在排序列表中相邻的两个(或多个)具有相同powers
的Monomal相邻。为了定义幂次(powers
是整数的三元组),我首先定义一个Cantor配对函数:
cantorPairing :: (Int, Int) -> Int
cantorPairing (k1,k2) = (k1+k2)*(k1+k2+1) + 2*k2
cantorPairing' :: (Int, Int, Int) -> Int
cantorPairing' (k1,k2,k3) = cantorPairing(cantorPairing(k1,k2),k3)
然后我可以通过在Cantor配对功能下比较两个powers
的值来进行比较:
groupBy ((==) `on` getPowers)
(sortBy (compare `on` (cantorPairing' . getPowers)) (toListOfMonomials p))
这给出了理想的结果:通过这个结果,我可以轻松地将两个具有相同powers
的单项式求和。
但这看起来很沉重,不是吗?是否没有groupBy
函数将非相邻元素也分组?否则,是否有更快速的方法来达到期望的结果?
答案 0 :(得分:1)
现在我无法想象通用的groupBy
函数会比O(n ^ 2)时间更快,但是您可以使用类似的东西
groupBy2 :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy2 = go [] where
go acc comp [] = acc
go acc comp (h:t) =
let (hs, nohs) = partition (comp h) t
in go ((h:hs):acc) comp nohs
它的工作方式与常规groupBy
完全相同,但是它加入了不相邻的元素类。
但是,如果您要使用on
函数,该问题将变得更加简单,因为我们可能会将其结果用作地图的键:
import qualified Data.Map as M
groupOn :: (Ord b) => (a -> b) -> [a] -> [[a]]
groupOn f =
let unpack = fmap snd . M.toList
fld m a = case M.lookup (f a) m of
Nothing -> M.insert (f a) [a] m
Just as -> M.insert (f a) (a:as) m
in unpack . foldl fld M.empty
这等效于
groupOn' f = groupBy2 ((==) `on` f)
(模排序)
顺便说一句–三胞胎和双胞胎已经定义了Ord实例,您可以像Int
s一样比较它们