【问题标题】:How to extract the maximum element from a List in haskell?如何从haskell中的列表中提取最大元素?
【发布时间】:2021-12-23 18:30:08
【问题描述】:

我是 Haskell 的新手,我想从给定的 List 中提取最大元素,以便最终得到最大元素 x 和剩余列表 xs(不包含 x)。可以假设列表的元素是唯一的。

我要实现的函数类型有点像这样:

maxElement :: (Ord b) => (a -> b) -> [a] -> (a, [a])

值得注意的是,第一个参数是将元素转换为可比较形式的函数。此外,此函数是非全部的,因为如果给定一个空的 List,它会失败。

我当前的方法无法将剩余列表中的元素保留在适当的位置,这意味着给定[5, 2, 4, 6] 它返回(6, [2, 4, 5]) 而不是(6, [5, 2, 4])。此外,感觉应该有一个更好看的解决方案。

compareElement :: (Ord b) => (a -> b) -> a -> (b, (a, [a])) -> (b, (a, [a]))
compareElement p x (s, (t, ts))
  | s' > s    = (s', (x, t:ts))
  | otherwise = (s, (t, x:ts))
  where s' = p x

maxElement :: (Ord b) => (a -> b) -> [a] -> (a, [a])
maxElement p (t:ts) = snd . foldr (compareElement p) (p t, (t, [])) $ ts

更新

感谢@Ismor 的回答和@chi 评论的帮助,我更新了我的实现,我对结果感到满意。

maxElement :: (Ord b) => (a -> b) -> [a] -> Maybe (b, a, [a], [a])
maxElement p =
 let
    f x Nothing = Just (p x, x, [], [x])
    f x (Just (s, m, xs, ys))
      | s' > s = Just (s', x, ys, x:ys)
      | otherwise = Just (s, m, x:xs, x:ys)
      where s' = p x
  in
    foldr f Nothing

当给定列表为空时,结果要么是Nothing,要么是Maybe (_, x, xs, _)。我可以使用最初预期的类型编写另一个“包装器”函数并在后台调用maxElement,但我相信这也可以。

【问题讨论】:

  • 如果有多个元素的最大值(例如[2, 5, 6, 3, 2, 6, 1, 3])会发生什么?
  • 那么,只提取其中一个,是第一个还是最后一个都无所谓。
  • @MarkSeemann 你也可以假设没有重复。
  • let max = getMax $ foldMap Max xs :: Int in (max, filter (/= max) xs)
  • 我认为避免foldr 并通过直接递归进行会更简单。如果您确实想使用foldr,您可能应该考虑将您的 3 元组 (b,a,[a]) 替换为 4 元组 (b,a,[a],[a]),其中两个列表是 1) 已删除最大值的列表和 2) 未删除的列表任何事物。折叠后,您可以从 4 元组中提取所需的输出,丢弃一些组件。

标签: list haskell functional-programming


【解决方案1】:

这个答案更多的是个人建议,而不是正确答案。根据经验,每当您发现自己尝试使用累加器编写循环时(如本例所示),请尝试以这种形式编写它

foldr updateAccumulator initialAccumulator --use foldl' if it is better for your use case`

然后,按照下面的类型完成它

步骤 1

在需要的地方写undefined。你知道函数应该是这样的

maxElement :: (Ord b) => (a -> b) -> [a] -> (a, [a])
maxElement f xs = foldr updateAccumulator initalAccumulator xs
 where 
  updateAccumulator  = undefined
  initialAccumulator = undefined

第二步

“追逐类型”。这意味着使用 maxElementfoldr 的类型你可以 推导出updateAccumulatorinitialAccumulator的类型。尽量减少多态性。在这种情况下:

  • 你知道foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
  • 您知道您的 Foldable[],所以替换起来会更容易
  • 因此foldr :: (a -> b -> b) -> b -> [a] -> b
  • 因为你想让foldr产生(a, [a])你知道b ~ (a, [a])
  • 等...继续进行,直到您知道您的函数有哪些类型。你可以在这个过程中使用 ghc typed hole,这是一个非常不错的功能
maxElement :: (Ord b) => (a -> b) -> [a] -> (a, [a])
maxElement f xs = foldr updateAccumulator initalAccumulator xs
 where 
  -- Notice that you need to enable an extension to write type signature in where clause
  -- updateAccumulator :: a -> (a, [a]) -> (a, [a])
  updateAccumulator newElement (currentMax, currentList) = undefined
  -- initialAccumulator  :: (a, [a])
  initialAccumulator = undefined

第三步

现在,写下函数应该更容易了。下面我留下一些不完整的部分供大家填写

maxElement :: (Ord b) => (a -> b) -> [a] -> (a, [a])
maxElement f xs = foldr updateAccumulator initalAccumulator xs
 where 
  -- updateAccumulator :: a -> (a, [a]) -> (a, [a])
  updateAccumulator newElement (currentMax, currentList) = 
    if f newElement > f currentMax
      then undefined -- How does the accumulator should look when the new element is bigger than the previous maximum?
      else undefined
  -- initialAccumulator  :: (a, [a])
  initialAccumulator = undefined -- Tricky!, what does happen if xs is empty?

希望这可以澄清一些疑问,并理解我没有给你一个完整的答案。

【讨论】:

  • 我看不出这种方法是如何工作的。 OP 想要一对(a,[a]),其中第二个组件是原始列表,按原始顺序删除了最大元素。我不认为updateAccumulator 的定义可以实现这一点。在xs=[3,2,1]xs=[3,1,2] 这两种情况下,我们都有相同的累加器currentMax=2, currentList=[1],所以我们不能同时返回(3,[2,1])(3,[1,2])。另请参阅我对上述 OP 的评论。
  • @chi 确实我误解了这个问题,但同样的原则也适用于 OP 最终解决方案。
  • @Lando-L 不接受我的回答,因为它实际上并不能解决您的问题。如果有人遇到与您相同的问题,那将是令人困惑的。顺便说一句,我发现你发布的灵魂非常聪明!
  • @Lando-L 你应该发布你的代码作为答案。
【解决方案2】:

我不知道您是否试图避免使用某些库函数,但 Data.List 有一个 maximumBydeleteBy 可以满足您的要求:

import Data.Function (on)
import Data.List (deleteBy, maximumBy)
import Data.Ord (comparing)

maxElement :: (Ord b) => (a -> b) -> [a] -> (a, [a])
maxElement f xs = (max, remaining) where
  max = maximumBy (comparing f) xs
  remaining = deleteBy ((==) `on` f) max xs

【讨论】:

  • 我不一定要避免库函数,但我正在寻找一种只遍历列表一次的解决方案。渐近地它仍然是 O(n),但如果 f: a -> b 是一个复杂函数,则每个元素必须计算两次。至少我是这么相信的。编译器也可能足够聪明,可以以某种方式重写它。
  • @Lando-L “每个元素计算两次”比这更糟。此答案中两个调用中的每个调用中的每个比较都会调用 f 两次。因此,对f 的总体调用次数增加了 4 倍,而不是 2 倍。避免此问题但仍可使用 HOF 的一种常见方法是通过 decorate-undecorate 范例。
【解决方案3】:

感谢@Ismor 的回答和@chi 评论的帮助,我更新了我的实现,我对结果感到满意。

maxElement :: (Ord b) => (a -> b) -> [a] -> Maybe (b, a, [a], [a])
maxElement p =
 let
    f x Nothing = Just (p x, x, [], [x])
    f x (Just (s, m, xs, ys))
      | s' > s = Just (s', x, ys, x:ys)
      | otherwise = Just (s, m, x:xs, x:ys)
      where s' = p x
  in
    foldr f Nothing

当给定列表为空时,结果要么是 Nothing,要么是 Maybe (_, x, xs, _)。我可以使用最初预期的类型编写另一个“包装器”函数并在后台调用 maxElement,但我相信这也可以。

【讨论】:

    【解决方案4】:

    在输入列表上构建所有“拉链”的列表,然后取其中的maximumBy (comparing (\(_,x,_) -> foo x)),其中foo 是您的Ord b => a -> b 函数,然后将前半部分反向附加到第二部分并放入与中间元素一起在一个元组中。

    列表上的拉链xs 是三重(revpx, x, suffx) 其中xs == reverse revpx ++ [x] ++ suffx

    > :t comparing (\(_,x,_) -> x)
    comparing (\(_,x,_) -> x)
      :: Ord a => (t, a, t1) -> (t, a, t1) -> Ordering
    

    构造拉链列表的是an elementary exercise(参见那里的函数picks3)。


    关于您编辑的解决方案,它可以在tails 上编码为foldr,这样会更清楚那里发生了什么:

    maxElement :: (Ord b) => (a -> b) -> [a] -> Maybe (b, a, [a])
    maxElement p [] = Nothing
    maxElement p xs = Just $ foldr f undefined (tails xs)
     where
        f [x]     _   =  (p x, x, [])
        f (x:xs) (b, m, ys)
          | b' > b    =  (b', x, xs)   -- switch over
          | otherwise =  (b, m, x:ys)
          where b' = p x
    

    它也更简洁一些,因为它不会无缘无故地返回输入列表的副本,就像您的版本一样,因为它用于内部目的。

    这两种方式实际上都是在模拟paramorphism

    【讨论】:

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