我构建了一个函数来验证可折叠结构的所有元素是否相等。
与列表中的类似功能相比,在我看来,更通用的功能不成比例地复杂,但我无法对其进行简化。
您有什么建议吗?
import Data.Monoid
import Data.Sequence as SQ
import Data.Matrix as MT
allElementsEqualL :: Eq a => [a] -> Bool
allElementsEqualL [] = True
allElementsEqualL (x:ns) = all (== x) ns
-- allElementsEqualL [1,1,1] -> True
allElementsEqualF :: (Foldable t, Eq a) => t a -> Bool
allElementsEqualF xs = case (getFirst . foldMap (First . Just) $ xs) of
Nothing -> True
Just x -> all (== x) xs
-- allElementsEqualF [1,1,1] -> True
-- allElementsEqualF $ SQ.fromList [1,1,1] -> True
-- allElementsEqualF $ MT.fromLists [[1,1],[1,1]] -> True
答案 0 :(得分:14)
我不太了解复杂程度,但是我认为这是最“干净”的方法。 “干净”是指使用单个特殊的Monoid
在结构上进行遍历。
data Same a = Vacuous | Fail | Same a
instance Eq a => Semigroup (Same a) where
Vacuous <> x = x
Fail <> _ = Fail
s@(Same l) <> Same r = if l == r then s else Fail
x <> Vacuous = x
_ <> Fail = Fail
instance Eq a => Monoid (Same a) where
mempty = Vacuous
allEq :: (Foldable f, Eq a) => f a -> Bool
allEq xs = case foldMap Same xs of
Fail -> False
_ -> True
答案 1 :(得分:7)
关于您的第一个功能的便利之处是您拥有的便利的方法是获得列表的“头”,而您的第二个不存在。幸运的是,我们可以为ScriptIntrinsicBlur.create(rs, allocation.getElement())
做同样的事情。让我们写一个Foldable
,它可以在任何head'
上运行(并且出于类型安全的考虑,我们将让Foldable
返回head'
)
Maybe
现在,我们可以编写与一般情况下的列表大小写基本相同的代码。
head' :: (Foldable t, Eq a) => t a -> Maybe a
head' = foldr (\a _ -> Just a) Nothing
从语法上看,它看起来有所不同,但这与您在列表中所做的完全相同:检查结构是否为空,如果不是,则检查每个元素是否等于第一个元素。
请注意,从技术上讲,这不等同于您发布的代码,因为它会将第一个元素与其自身进行比较。因此,如果您的allElementsEqualF :: (Foldable t, Eq a) => t a -> Bool
allElementsEqualF f = case head' f of
Nothing -> True
Just a -> all (== a) f
运算符由于某种原因而不是自反的,则会得到不同的结果(尝试在列表==
上运行我的代码和您的代码)
答案 2 :(得分:6)
Silvio的回答在语法上很小,很容易理解;但是,如果Foldable
实例不能便宜地计算head'
,它可能会做两倍的工作。在此答案中,我将讨论如何仅通过一次就可以执行计算,无论底层的Foldable
是否可以廉价地计算head'
。
基本思想是:我们不仅跟踪“到目前为止所有元素是否相等”,而且还跟踪它们全部相等。所以:
data AreTheyEqual a
= Empty
| Equal a
| Inequal
deriving Eq
这是一个Monoid
,以Empty
为单位,Inequal
作为吸收元素。
instance Eq a => Semigroup (AreTheyEqual a) where
Empty <> x = x
x <> Empty = x
Equal a <> Equal b | a == b = Equal a
_ <> _ = Inequal
instance Eq a => Monoid (AreTheyEqual a) where
mempty = Empty
现在我们可以使用foldMap
来总结整个Foldable
,就像这样:
allElementsEqual :: (Eq a, Foldable f) => f a -> Bool
allElementsEqual = (Inequal /=) . foldMap Equal
答案 3 :(得分:4)
一个相当琐碎的选择,并且我通常更喜欢其他答案之一,就是重用allElementsEqualL
:
allElementsEqualF = allElementsEqualL . toList
或内联后
allElementsEqualF xs = case toList xs of
[] -> True
x:xs' -> all (== x) xs'
懒惰使它变得合理。 all
调用并不需要整个xs'
,而是直到找到第一个与x
不同的调用。因此,toList
也不会要求整个xs
。同时,已经检查过的元素不需要保留在内存中。
您可以编写一个Foldable
实例,该实例的toList
懒惰程度没有必要,但是除了那些情况外,我认为它应该做的工作与Daniel Wagner和HTNW的回答一样多(开销很小)不取决于输入大小。
我认为也是一种混合解决方案:
allElementsEqualF2 xs | F.null xs = True | otherwise = all (== x) xs where x = head $ F.toList xs
因此,如果goList是惰性的,则将对原始类型(全部)进行测试。
在非空情况下,这比Silvio的回答要做的工作要多一些,因为F.null
复制F.toList
的工作量与head'
一样多。因此,Silvio的代码必须到达第一个元素2次(一个用于head'
,另一个在all
内部),而您的代码执行3次(null
,head $ toList xs
和{ {1}})。