【问题标题】:Haskell - Checking if all list elements are uniqueHaskell - 检查所有列表元素是否唯一
【发布时间】:2015-06-24 20:32:51
【问题描述】:

我需要比较给定列表的所有元素是否都是唯一的。 (为了记录,我这样做是出于学术目的。)

这是我目前所拥有的:

allDifferent :: (Eq a) => [a] -> Bool
allDifferent list = case list of
    []      -> True
    (x:xs)  -> if x `elem` xs then False else allDifferent xs

效果很好!

现在,当我尝试这样做时......

allDifferent2 :: (Eq a) => [a] -> Bool
allDifferent2 list
    | null list                                                     = True        
    | (head list) `elem` (tail list) || allDifferent2 (tail list)  = False
    | otherwise  

它只是没有按预期工作。 我从 GHCi 得到以下输出:

*Main> allDifferent2 [1..4]
False
*Main> allDifferent2 [1..5]
True
*Main> allDifferent2 [1..6]
False
*Main> allDifferent2 [1..7]
True

即对于具有偶数个元素的每个列表,它输出 False,对于奇数个元素,它输出 True。

我错过了什么? 有人愿意发光吗?

【问题讨论】:

  • 不应该是null list = True来匹配你原来的实现吗?
  • 是的,抱歉.. 错字.. 刚刚修复了这个问题,但是错误仍然存​​在。
  • 所以你有head list `elem` tail list || allDifferent2 (tail list) = False,它基本上是在说“如果第一个元素在列表中的某个地方,或者如果列表的所有其余部分都充满了唯一性,那么返回 False”。你确定这是你想要的行为吗?您可以使用head list `elem` tail list = allDifferent2 (tail list) 使其更简单,我认为这对您更有效。
  • 如果您对 allDifferent2 的递归调用返回 True,则警卫通过,然后您返回 False,这可能不是您想要的。

标签: list haskell functional-programming comparison


【解决方案1】:

另一种利用notElem

allDifferent :: (Eq a) => [a] -> Bool
allDifferent list = case list of
    []      -> True
    (x:xs)  -> x `notElem` xs && allDifferent xs

次要变体,直接在方程式中使用模式匹配:

allDifferent :: (Eq a) => [a] -> Bool
allDifferent []     = True
allDifferent (x:xs) = x `notElem` xs && allDifferent xs

我倾向于远离像 head,tail 这样的部分函数,​​所以基于守卫的变体对我来说看起来更糟。

【讨论】:

  • 有意思,使用偏函数有什么危害?
  • @LuisDosReis 如果你要求head []tail [],整个程序会中止(崩溃)。 (详尽的)模式匹配不会发生这种情况。 (而-Wall 有助于检测非详尽性)。
  • @LuisDosReis 在保护解决方案中,请记住您必须添加 | otherwise = blah 案例
  • @LuisDosReis 如果你的代码以一个守卫列表结尾,最后一个必须等​​同于otherwise,因为它必须捕获所有剩余的情况。您可以使用更复杂的检查,但没有真正的理由这样做。
  • @LuisDosReis 使用总功能要容易得多。事实上,您可以从总函数的类型推导出定理,并确保它具有某些属性,只需查看类型即可。如果函数是部分的,这种事情会变得更难,因为函数可能随时崩溃,而且您通常不能轻易地导出属性。这使得推理和证明正确性变得更加困难。
【解决方案2】:

我会以不同的方式做这件事。递归 + elemO(n²)。或者,您可以先对列表进行排序,然后成对比较元素。这样排序是O(n⋅log n),遍历O(n)。所以总体O(n⋅log n)

import Data.List

allDifferent :: (Ord a, Eq a) => [a] -> Bool
allDifferent = comparePairwise.sort

comparePairwise :: Eq a => [a] -> Bool
comparePairwise [] = True
comparePairwise [_] = True
comparePairwise (x:y:xs) 
    | x == y = False
    | otherwise = comparePairwise (y : xs)

【讨论】:

  • 我建议comparePairwise (x:xs@(y:_)) = x /= y && comparePairwise xs ; comparePairwise _ = True
  • @chi 同意。它更优雅。
  • 通常的做法是comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs)
  • @Luis Dos Reis 渐近时间仍然是线性的,所以从算法的角度来看没有性能变化。
  • 但是,由于Ord 约束,这不太通用。
【解决方案3】:

您可以依赖库函数:allDifferent xs = nub xs == xs

或者,用无点表示法写成:allDifferent = uncurry (==) . (nub &&& id)

使用Data.Discrimination.nub,这会在 O(n) 时间内发生。

【讨论】:

    【解决方案4】:

    试试这个:

    allDifferent2::(Eq a) => [a] -> Bool
    allDifferent2 list
        | list == []                        = True
        | (head list) `elem` (tail list)    = False
        | otherwise = allDifferent2(tail list)
    

    如果列表是 [] 你应该返回 True (正如@bheklilr 所说:))

    如果列表不为空,您可以验证第一个元素是否在列表的尾部。如果是,则返回 False。好的。

    但是当你说“如果它在列表的尾部OR allDifferent2(尾部列表)”时,你正在扼杀你的功能。 “如果此列表中的所有元素都不同,则返回 FALSE”,这不是您想要的。

    编辑:是的,它会@Luis。我通过将“否则”放在那里来解决这个问题。当我将警卫放在 allDifferent2(tail list) 之前时,它会检查此函数是否返回 True。因此它适用于 [1, 1, 2] (我的测试用例),但不适用于 [1, 2, 2] (类似于你的情况)。

    【讨论】:

    • 我在您输入时复制了它。然而,当我输入一个包含不唯一元素的列表时,GHCi 会引发非详尽模式异常。*Main> allDifferent3 [1, 2, 3, 4, 4]*** Exception: allDiff.hs:(18,1)-(21,47): Non-exhaustive patterns in function allDifferent3
    • 确实如此。我编辑了我的答案,现在应该没问题了。我用 [1, 1, 2] (从重复的数字开始)而不是 [1, 2, 2] 测试了重复情况。但现在是对的。 :)
    【解决方案5】:

    我能想到的最简单合理的惯用方法是

    allDifferent :: Ord a => [a] -> Bool
    allDifferent = pairwiseDifferent . sort
    
    pairwiseDifferent :: Eq a => [a] -> Bool
    pairwiseDifferent xs = and $ zipWith (/=) xs (drop 1 xs)
    

    为了有趣的折叠,

    import Data.Maybe
    
    pairwiseDifferent xs = foldr go (const True) xs Nothing
      where
        go x k Nothing = k (Just x)
        go x k (Just prev) = x /= prev && k (Just x)
    

    另一种选择是使用Set(某些严格性注释实际上可能不需要):

    import qualified Data.Set as S
    
    allDifferent xs = foldr go (\s -> s `seq` True) xs S.empty
      where
        go x k s
           | S.member x s = False
           | otherwise = k $! S.insert x s
    

    【讨论】:

      【解决方案6】:

      对列表进行排序,group 将相同的元素运行在一起,并检查 all 组是否只有一个元素。

      import Data.List (group, sort)
      
      pairwiseDistinct :: Ord a => [a] -> Bool
      pairwiseDistinct xs = all (\ys -> null (tail ys)) (group (sort xs))
      

      免积分版:

      pairwiseDistinct = all (null . tail) . group . sort
      

      这假设对于任何两个元素 xyx == y 当且仅当 compare x y == EQ

      tail 在这里很好,因为任何组都不会是空的,但如果您不喜欢偏函数,可以替换 drop 1

      【讨论】:

      • 有效,但仅当元素类型为Ord
      【解决方案7】:
      allDifferent []    = True
      allDifferent (h:t) = 
                           let (e,(l,r)) = segment h t 
                           in e && allDifferent l && allDifferent r
      
      segment p []     = (True,([],[])))
      segment p (h:s) 
         | p > h       = let (e,(l,r)) = segment p s in (e,(l,h:r))
         | p < h       = let (e,(l,r)) = segment p s in (e,(h:l,r))
         | otherwise   = (False,([],[])))
      

      如您所见,此解决方案的结构与 quickSort 非常相似。 它共享二叉树作为中间数据结构,因此时间复杂度非常相似。

      【讨论】:

        猜你喜欢
        • 2011-07-13
        • 2020-04-28
        • 2019-12-22
        • 2018-04-24
        • 2018-01-10
        • 2023-03-31
        • 2011-04-20
        • 1970-01-01
        相关资源
        最近更新 更多