【问题标题】:Checking for empty list in Haskell: Is (length list == 0) or (list == []) more efficient?在 Haskell 中检查空列表: (length list == 0) 或 (list == []) 更有效吗?
【发布时间】:2016-04-07 05:00:19
【问题描述】:

假设我想在 Haskell 的守卫中检查列表是否为空,有两个选项:

  1. length list == 0
  2. list == []

这两个逻辑测试中哪一个更有效?我倾向于说空列表测试,因为它依赖于更基本的构造而不是前奏函数length,但我不确定。

【问题讨论】:

  • 1 在列表的长度上是线性的(显然),而 2 是常数时间。但是 2 有一个陷阱,即当您并不真正需要它时需要 Eq 约束。使用null 或简单地通过模式匹配来检查空列表。
  • 问题是当您想将相等性与其他预定义列表进行比较时,模式匹配不起作用
  • 也谢谢你,这很有意义。你能详细说明 null 吗?
  • 确实,如果您实际上是在比较相等性(而不仅仅是检查是否为空),那么 == 是要走的路。 null(== []) 一样需要恒定时间,但没有 Eq 约束。此外,在最新的几个 GHC 版本中,null 被泛化为与任何Foldable 一起使用,例如null :: Maybe a -> Bool 如果输入为 Nothing,则返回 true。
  • @user2407038 这应该是一个答案。

标签: haskell optimization


【解决方案1】:

length list == 0需要遍历整个列表才能得到它的长度,也就是说它是O(n)list == [] 对元素类型产生 Eq 约束。 null list 在恒定时间内运行并且没有类型类约束。

但是,有一个巧妙的技巧可以做类似length list == 0 的事情,它的优点是它可以很好地推广到length list1 == length list2,而无需经过更长的列表:您可以使用genericLength 来充分懒惰地表示自然数这样比较只会强制遍历较短的列表。

一个例子是使用the Natural type:

import Data.Number.Natural
import Data.List (genericLength)

nats :: [Int]
nats = iterate succ 0

areThereTenNats :: Bool
areThereTenNats = genericLength nats >= (10 :: Natural)

【讨论】:

  • 任何“足够懒惰的自然数表示”都会在低效和荒谬的低效之间变化。此外,OP 问题的任何替代解决方案都必须至少完全按照 null 所做的那样做。
  • @DerekElkins,通过第一个参数上的模式匹配和第二个参数中的“参数”进行加法的传统实现可以正常工作。不过,您必须阅读genericLength 源代码和+ 实现源代码来验证这一点。仅使用参数将forall a . [a] 视为自然数显然更正确。
  • @dfeuer 我想我应该更清楚的是,对于“==0”的情况,虽然它不会比null 快,但它不会过于低效。对于任何其他情况,它都是搅动垃圾和浪费计算的秘诀,因为这些东西应该并行地沿着两个指针向下移动,不需要分配。不过,它确实具有与指针行走相同的渐近效率。我只是想表明,如果您担心效率,切换到“懒惰自然”类型与您应该做的完全相反。 (我不是说 Cactus 是这么说的。)
  • @dfeuer:非零数(例如 1)如何表示为forall a. [a]
  • @Cactus,我的意思是exists a . [a],或者使用forall 的各种方法。
【解决方案2】:

您可以使用null list 在恒定时间内检查您的列表是否为空,它返回一个布尔值。

Prelude> null []
True
Prelude> null [1]
False
Prelude> null ""
True
Prelude> null "Test"
False

【讨论】:

  • 是的,但是这个答案对dfeuer's 有什么作用?
  • 作为一个新手,我对此表示赞同,因为它清楚地展示了如何使用 null API。
【解决方案3】:

正如其他人所指出的,检查列表是否为空(仅此而已)的最佳方法是使用

null :: Foldable f => f a -> Bool

可以在类型中使用

null :: [a] -> Bool

如果你想检查一个列表是否为空,因为你想查看它的元素,你通常应该使用模式匹配来代替:

f [] = something
f (x : xs) = something using x and/or xs

如果您想比较两个列表的长度(仅此而已),最好的方法通常是

compareLength :: [a] -> [b] -> Ordering
compareLength [] [] = EQ
compareLength [] (_ : _) = LT
compareLength (_ : _) [] = GT
compareLength (_ : xs) (_ : ys) =
  compareLength xs ys

检查列表长度与某个数字的比较的最佳方法是

compareToLength :: Foldable f
                => f a -> Int -> Ordering
compareToLength = foldr go (compare 0) where
  go _ r n | n <= 0 = GT
           | otherwise = r $! n - 1

【讨论】:

  • 如果您想要更简洁的内容,compareLength = compare `on` (() &lt;$) 是比较长度的另一种选择。
  • @分号,好点。你甚至可以把它作为一个答案。 compare (replicate n ()) (() &lt;$ xs) 这样也可能有用。这些都不会像手工操作那样有效,但也不会很糟糕。
猜你喜欢
  • 2022-01-14
  • 2014-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-29
  • 2019-02-02
  • 2021-12-21
  • 1970-01-01
相关资源
最近更新 更多