【问题标题】:Removing n number of elements from a list从列表中删除 n 个元素
【发布时间】:2014-11-05 02:16:33
【问题描述】:

今年我的一个模块是 Haskell 编程。我无法以如此简单的方式表达复杂性,尤其是来自 C# 等其他语言的复杂性。

熟悉过程的一部分涉及我们实现一个 drop' 函数,该函数从列表中删除 n 个元素。

写一个函数 drop' :: Int -> [a] -> [a],其中 drop' n xs 返回的 xs 删除了它的前 n 个元素。

到目前为止,我想出的是这个。

drop' :: Int -> [a] -> [a]
drop' x [] = []
drop' x (y : ys) = if x == 0 then ys else drop' (x-1) ys  

我知道我需要在列表的尾部递归调用 drop' 以有效地删除一个元素,但我不知道如何计算已删除的元素数量,以便确保删除 n 个元素。

我知道上面没有给我预期的结果,因为它比我预期的多删除了 1,但有趣的是,这有效:

drop' :: Int -> [a] -> [a]
drop' x [] = []
drop' x (y : ys) = if x == 1 then ys else drop' (x-1) ys

我无法推断出原因!任何有关此逻辑的帮助将不胜感激!

【问题讨论】:

  • 从5数到0,会有六个数。因此,在第一种情况下,您要删除 6 个数字,而在第二种情况下,您要删除 5 个(从 5 到 1)。除此之外,我猜第一个错误,If x == 0 then ys,也许?
  • 条件确保元素只有在 x 不等于 0 时才会被移除,所以在第一种情况下应该是:5 = 1 移除,4 = 2 移除,3 = 3 移除,2 = 4 删除,1 = 5 删除。但事实并非如此!

标签: haskell


【解决方案1】:

在您的代码中:

drop' x (y : ys) = If x == 0 then ys else drop' (x-1) ys  

这是您的一对一错误的来源:如果 x 为 0,您希望返回整个列表不变 (y:ys),而不仅仅是尾部 (ys)。等效地,您可以检查 1 而不是 0,然后返回 ys

使用模式匹配而不是if 会更惯用,您应该使用_ don't-care 变量来表示您不使用的值:

drop' :: Int -> [a] -> [a]
drop' _ [] = []            -- dropping any number of items from [] is still empty
drop' 0 lst = lst          -- dropping nothing returns the list unchanged
drop' n (_:xs) = drop' (n-1) xs  -- otherwise remove head and recurse on the tail

【讨论】:

  • 我已经编辑了 xs 错误。这是完全有道理的,但是为了熟悉 Haskell,我们不能使用 Haskell 中提供的 drop 函数!
  • @Bradley - 我不是故意的;没有撇号的drop 是一个错字。检查编辑的答案。
  • 在您的回答中,您使用了下划线关键字而不是值,这是什么效果以及为什么与使用 x 有什么不同。编辑您是否使用 _ 因为我们将其从列表中删除,因此我们不关心它的价值?
  • 如果您使用-W 启用额外警告,如果您不使用名称,GHC 会警告您,除非它以下划线开头。
  • 那是布拉德利,不是我,@Wyzard。但是,是的,_ 只是表示“我没有使用这个值”。对于解释器/编译器和阅读代码的人来说,这是一个明确的信号,即该值对于这种情况并不重要。
【解决方案2】:

你不需要记录到目前为止你已经删除了多少元素,只需要删除多少元素,这就是x 参数。所以你已经有了那部分。

第一个版本的函数的问题是,当 x 为 0 时,您返回 ys,但这只是列表的尾部,因为您已经通过模式匹配删除了第一个元素参数列表。您需要使用 if x == 0 then (y:ys) 来返回整个输入列表。

或者,您可以在递归情况下使用tail 函数而不是模式匹配:

drop' x ys = if x == 0 then ys else drop' (x-1) (tail ys)

您可以将零大小写放在单独的一行而不是使用if,所以整个定义是:

drop' :: Int -> [a] -> [a]
drop' _ [] = []
drop' 0 ys = ys
drop' x ys = drop' (x-1) (tail ys)

【讨论】:

  • 这解释得很好,谢谢!出于某种原因,我认为当 x = 0 时返回列表的尾部实际上是整个列表,而实际上不是。
  • 如果给定列表为空且第一个参数大于零,您的drop' 将失败。这不是真正的drop 的行为。
  • @Mark,我打算将这两行作为问题中单行的替代品,而不是函数的完整定义,因此它们隐含在类型前面声明和空列表案例。但我可以看到这可能不清楚,所以我编辑了答案以提供所有四行的完整定义。
【解决方案3】:

即使不使用递归,以下方法也可能具有启发性。考虑一下这种理解,

drop' :: Int -> [a] -> [a]
drop' n xs = [ v | (_,v)  <- filter (\(x,_) -> x > n) $ zip [1..] xs ]

本例中感兴趣的质量,

  • 我们(懒惰地)压缩列表xs,其索引值从1 开始直到xs 中的元素数量;
  • filter 包含一个 lambda 函数,该函数提取 zipped 元组中的第一个元素,并将该值与要删除的元素数进行比较;
  • 对于每个过滤后的元组,我们提取第二个元素,即原始列表中的一个值。

那么例如

drop' 4 ['a'..'t']
"efghijklmnopqrst"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 2022-07-24
    • 2023-03-25
    相关资源
    最近更新 更多