【问题标题】:Check if a list of lists has two or more identical elements检查列表列表是否具有两个或多个相同的元素
【发布时间】:2019-05-16 10:29:38
【问题描述】:

我需要编写一个函数来检查一个列表是否有两个或多个相同的元素并返回真或假。

例如 [3,3,6,1] 应该返回 true,但 [3,8] 应该返回 false。

这是我的代码:

identical :: [Int] -> Bool
identical x = (\n-> filter (>= 2) n )( group x )

我知道这很糟糕,但它不起作用。 我想将列表分组为列表列表,如果列表的长度> = 2,那么它应该返回 true,否则返回 false。

【问题讨论】:

  • 两个或多个连续相等的元素?还是可以将相同的元素放在任何地方?
  • 第二个,是否连续无所谓。
  • 提示:这里的n 是什么?列表[Int]?或长度Int?如果你过滤这个,结果会是什么?
  • 可能是长度 Int?我试过[n],但也没用。

标签: list haskell filter


【解决方案1】:

使用any 获得布尔结果。

any ( . . . ) ( group x )

不要忘记对列表进行排序,group 适用于连续元素。

any ( . . . ) ( group ( sort x ) )

您可以将 (not .null .tail) 用于谓词,作为选项之一。

【讨论】:

  • 这行得通,但我不明白not . null . tail 是做什么的。
  • null 如果列表为空,则返回 True,我们检查列表在第一个之后是否还有元素。相当于(\x -> (tail x) /= [])
  • 我可能会选择(not . null . drop 1),因为drop 1tail 不同。但是由于group 的语义(它从不产生空列表),在这种情况下并不重要。
  • 这是一种简洁易读且简单的方法。尽管要求对列表进行排序是相当低效的,但请牢记这一点。这可能是一个简单的线性算法,它甚至可以在无限列表中工作(不是排序的情况)。更糟糕的是,一些排序算法只允许您在列表完全排序后访问元素,GHC 实现的归并排序就是这种情况。
  • @JorgeAdriano 排序的效率不亚于重复的Set 插入(这是 O(lg n),而不是 O(1),使用来自 containers 库的 Set 类型,无论如何)。
【解决方案2】:

就在昨天我发布了一个类似的算法here。一种可能的方法是,

  • 生成元素的累积集合序列
    {}, {x0}, {x0,x1}, {x0,x1,x2} ...
  • 将原始元素序列与累积集配对
    x0, x1 , x2 , x3 ...
    {}, {x0}, {x0,x1}, {x0,x1,x2} ...
  • 检查重复插入,即
    xi 这样xi ∈ {x0..xi-1}

这可以通过下面的函数来实现。 首先,我们使用scanl 迭代地将列表的元素添加到一个集合中,从而产生这些迭代的累积序列。

sets  :: [Int] -> [Set Int]
sets  = scanl (\s x -> insert x s) empty

然后我们用这个序列压缩原始列表,因此每个xi 都与{x0...xi-1} 配对。

elsets :: [Int] -> [(Int, Set Int)] 
elsets xs  = zip xs (sets xs)

最后,我们使用find 在已经包含它的集合中搜索“即将插入”的元素。函数find返回对元素/集合,我们模式匹配只保留元素,并返回它。

result ::  [Int] -> Maybe Int
result xs = do (x,_) <- find(\(y,s)->y `elem` s) (elsets xs)
               return x

【讨论】:

    【解决方案3】:

    另一种使用Data.Map 的方法比..group . sort.. 解决方案效率低,它仍然是O(n log n),但可以使用无限列表。

    import Data.Map.Lazy as Map (empty, lookup, insert)
    
    identical :: [Int] -> Bool
    identical = loop Map.empty
        where loop _ []     = False
              loop m (x:xs) = if Map.lookup x m == Nothing
                              then loop (insert x 0 m) xs
                              else True
    

    【讨论】:

      【解决方案4】:

      好的,基本上这是您真正需要sort 以提高效率的罕见情况之一。事实上,Data.List.Unique 包有一个 repeated 函数专门用于这项工作,如果检查源,可以看到选择了 sortgroup 策略。我想这不是最有效的算法。我将讨论如何让sort 更有效率,但暂时让我们享受一下,因为这是一个很好的问题。

      所以我们在Data.List 包中有tails :: [a] -&gt; [[a]] 函数。相应地;

      *Main> tails [3,3,6,1]
      [[3,3,6,1],[3,6,1],[6,1],[1],[]]
      

      您可能很快注意到,我们可以通过应用一个函数来检查all 项目是否不同,从而将tailstails 列表tailtail [[3,6,1],[6,1],[1],[]] 与给定的原始列表一起使用。这个函数可以是一个列表推导或者只是all :: Foldable t =&gt; (a -&gt; Bool) -&gt; t a -&gt; Bool 函数。问题是,我想把zipWith 短路,这样一旦我遇到第一个骗子,让我们停止zipWith 通过检查其余部分来做浪费工作。为此,我可以使用zipWith 的一元版本,即zipWithM :: Applicative m =&gt; (a -&gt; b -&gt; m c) -&gt; [a] -&gt; [b] -&gt; m [c],它位于Control.Monad 包中。原因是,从它的类型签名我们了解到,如果我的 monad 恰好是 MaybeEither,则当它在中间考虑 NothingLeft whatever 时,它将停止进一步计算。

      哦..!在 Haskell 中,我也喜欢使用 bool :: a -&gt; a -&gt; Bool -&gt; a 函数而不是 ifthenbool 是 Haskell 的三元运算,类似于

      bool "work time" "coffee break" isCoffeeTime
      

      否定的选择在左边,肯定的选择在右边,其中isCoffeeTime :: Bool 是一个函数,如果是咖啡时间则返回True。也非常可组合.. 太酷了..!

      既然我们现在已经掌握了所有的背景知识,我们就可以继续编写代码了

      import Control.Monad (zipWithM)
      import Data.List (tails)
      import Data.Bool (bool)
      
      anyDupe :: Eq a => [a] -> Either a [a]
      anyDupe xs = zipWithM f xs ts
                   where ts = tail $ tails xs
                         f  = \x t -> bool (Left x) (Right x) $ all (x /=) t
      
      *Main> anyDupe [1,2,3,4,5]
      Right [1,2,3,4,5] -- no dupes so we get the `Right` with the original list
      *Main> anyDupe [3,3,6,1]
      Left 3 -- here we have the first duplicate since zipWithM short circuits.
      *Main> anyDupe $ 10^7:[1..10^7]
      Left 10000000 -- wow zipWithM worked and returned reasonably fast.
      

      但是再次......正如我所说,这仍然是一种幼稚的方法,因为理论上我们正在执行n(n+1)/2 操作。是的 zipWithM 如果第一个遇到的骗子靠近头部,则大大减少了冗余,但这个算法仍然是 O(n^2)。

      我相信在这种特殊情况下最好使用 Haskell 的天堂 sort 算法(顺便说一下,这不是我们所知道的归并排序)。

      现在算法奖颁给了 -> 这里的鼓声 -> sortfold -> 掌声。抱歉没有分组。

      所以现在......我们将再次使用一元技巧来利用短路。我们将使用foldM :: (Foldable t, Monad m) =&gt; (b -&gt; a -&gt; m b) -&gt; b -&gt; t a -&gt; m b。当与Either monad 一起使用时,这也允许我们返回更有意义的结果。好的,让我们一起做。任何Left n 表示n 是第一个欺骗,不再进行计算,而任何Right _ 表示没有欺骗。

      import Control.Monad (foldM)
      import Data.List (sort)
      import Data.Bool (bool)
      
      anyDupe' :: (Eq a, Ord a, Enum a) => [a] -> Either a a
      anyDupe' xs = foldM f i $ sort xs
                    where i = succ $ head xs -- prevent the initial value to be equal with the value at the head
                          f = \b a -> bool (Left a) (Right a) (a /= b)
      
      *Main> anyDupe' [1,2,3,4,5]
      Right 5
      *Main> anyDupe' [3,3,6,1]
      Left 3
      *Main> anyDupe' $ 1:[10^7,(10^7-1)..1]
      Left 1
      (2.97 secs, 1,040,110,448 bytes)
      *Main> anyDupe $ 1:[10^7,(10^7-1)..1]
      Left 1
      (2.94 secs, 1,440,112,888 bytes)
      *Main> anyDupe' $ [1..10^7]++[10^7]
      Left 10000000
      (5.71 secs, 3,600,116,808 bytes)   -- winner by far
      *Main> anyDupe $ [1..10^7]++[10^7] -- don't try at home, it's waste of energy
      

      在现实世界的场景中,anyDupe' 应该永远是赢家。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多