测试可折叠元素的所有元素是否相同

时间:2019-04-23 16:37:06

标签: haskell

我构建了一个函数来验证可折叠结构的所有元素是否相等。

与列表中的类似功能相比,在我看来,更通用的功能不成比例地复杂,但我无法对其进行简化。

您有什么建议吗?

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

4 个答案:

答案 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次(nullhead $ toList xs和{ {1}})。