【问题标题】:Counting number of elements in a list that satisfy the given predicate计算列表中满足给定谓词的元素数
【发布时间】:2012-01-30 20:49:09
【问题描述】:

Haskell 标准库是否有一个给定列表和谓词的函数,返回满足该谓词的元素数?类似于(a -> Bool) -> [a] -> Int 的类型。我的 hoogle 搜索没有返回任何有趣的内容。目前我正在使用length . filter pred,我认为这不是一个特别优雅的解决方案。我的用例似乎很常见,可以提供更好的库解决方案。是这样还是我的预感有误?

【问题讨论】:

    标签: haskell functional-programming


    【解决方案1】:

    length . filter p 的实现并不像您建议的那么糟糕。特别是,它在内存和速度方面只有恒定的开销,所以是的。

    对于使用流融合的东西,比如vector 包,length . filter p 实际上会被优化以避免创建中间向量。然而,列表目前使用所谓的foldr/build fusion,它不够聪明,无法优化length . filter p,而不会创建可能导致堆栈溢出的线性大thunk。

    有关流融合的详细信息,请参阅this paper。据我了解,目前 Haskell 主要库中未使用流融合的原因是(如本文所述)大约 5% 的程序在流之上实现时性能显着更差-基于库,而foldr/build 优化永远不会(AFAIK)使性能变得更糟。

    【讨论】:

    • 这确实是 Haskell 最酷的事情之一:您可以在自己的代码中指定优化器使用的重写规则,并使用它们来获得可组合且高效的代码。
    • 但重点是,您不应该为此编写自己的代码:您应该只使用length . filter p,并相信优化器。
    • 我会将count pred = length . filter pred 添加到我的实用程序中。
    • 这也行得通...但是提个建议:如果您在声明 count 上方使用 {-# INLINE count #-} 杂注,您将获得更好的结果。 (“更好的结果”是指“优化器有更多的机会去做它的事情。”)
    • @LouisWasserman:融合发生在“好消费者”消费“好生产者”的结果时。两者都列出了here,不包括length。另见:GHC #876: length is not a good consumer.
    【解决方案2】:

    不,没有预定义的函数可以做到这一点,但我想说length . filter pred 实际上是一个优雅的实现;它尽可能接近表达你的意思,而不仅仅是直接调用这个概念,如果你在定义它,你就无法做到这一点。

    唯一的选择是递归函数或折叠,IMO 会不太优雅,但如果你真的想要:

    foo :: (a -> Bool) -> [a] -> Int
    foo p = foldl' (\n x -> if p x then n+1 else n) 0
    

    这基本上只是将length 内联到定义中。至于命名,我建议count(或者countBy,因为count是一个合理的变量名)。

    【讨论】:

    • +1,看来我必须在我的实用程序中定义一个新函数。感谢您的确认,以及有关函数名称的建议。
    • 我建议不要在你的 utils 中定义一个新函数。 length . filter pred 会更清晰。如果您发现自己一遍又一遍地重复它,那么在相对狭窄的范围内定义它是合适的(例如,where 绑定或未导出的顶级函数)。但是如果你把它放在一个 utils 模块中,阅读你的代码的第三方将不得不挖掘它来弄清楚你的代码在做什么。
    【解决方案3】:

    Haskell 是一种高级语言。它不是为您可能遇到的每种可能的情况组合提供一个函数,而是为您提供了涵盖所有基础知识的一小部分函数,​​然后您可以根据需要将它们粘合在一起以解决当前手头的任何问题。

    就简单和简洁而言,这是最优雅的。所以是的,length . filter pred 绝对是标准解决方案。作为另一个例子,考虑elem,它(您可能知道)告诉您给定项目是否存在于列表中。这个的标准参考实现实际上是

    elem :: Eq x => x -> [x] -> Bool elem x = foldr (||) False 。地图(x ==)

    换句话说,将列表中的每个元素与目标元素进行比较,创建一个新的布尔列表。然后在这个新列表上折叠逻辑或函数。

    如果这看起来效率低下,请尽量不要担心。特别是,

    1. 编译器通常可以优化掉由此类代码创建的临时数据结构。 (请记住,这是在 Haskell 中编写代码的标准方式,因此编译器已针对它进行了调整。)

    2. 即使无法对其进行优化,懒惰通常也会使此类代码相当高效。

    (在这个特定示例中,OR 函数将在看到匹配项后立即终止循环 - 就像您自己手动编码会发生的情况一样。)

    作为一般规则,通过将预先存在的函数粘合在一起来编写代码。仅当性能不够好时才更改此设置。

    【讨论】:

    • 感谢您的回答,+1。我已经使用函数式编程足够长的时间了,并且很清楚您在回答中所说的内容,但是当我的模式与此处讨论的模式一样重复时,我将其分解为一个单独的函数。
    • Here 是 Factor 编程语言的哲学页面,这是一种以组合为 做事方式的语言,并且大多数代码都是以无点方式编写的。看看它。无论使用哪种语言,这些准则中的许多都适用,而我碰巧遵循了它们。您会在 Leo Brodie 的“Thinking Forth”中找到类似的建议,这是一本关于软件开发的好书。
    • a smallish set of functions that cover all of the basics, and you then glue these together as required to solve whatever problem is currently at hand 在我的书中这是低级的定义
    【解决方案4】:

    这是我对类似问题的业余解决方案。计算列表中的负整数个数 l

    nOfNeg l = length(filter (<0) l)
    main = print(nOfNeg [0,-1,-2,1,2,3,4] ) --2
    

    【讨论】:

      【解决方案5】:

      不,没有!

      截至 2020 年,Haskell 标准库中确实还没有这样的成语!但是可以(并且应该)插入一个成语howMany(类似于古老的any

      howMany p xs = sum [ 1 | x <- xs, p x ]
      -- howMany=(length.).filter
      
      main = print $ howMany (/=0) [0..9]
      

      Try howMany=(length.).filter

      【讨论】:

      • 正如我在您最近的另一个回答中所说,这不是 Code Golf。另外,提问者在问题中提到他们已经知道如何使用lengthfilter
      • @JosephSible-ReinstateMonica, length.filter p 没有请求的类型签名 (a -> Bool) -> [a] -> Int 但只有 [a] -> Int,但您可以是的,我应该扩展我的答案,即确实可以将这个 howMany 成语添加到库中?
      • 不,但\p -&gt; length . filter p 可以。
      • 我知道。就像 \p->\xs->sum[1|x
      • 我再说一遍:这不是 Code Golf!在这里,我们想要更容易理解的答案,而不是更简短的答案。
      猜你喜欢
      • 2018-07-21
      • 2020-06-18
      • 1970-01-01
      • 1970-01-01
      • 2011-11-09
      • 1970-01-01
      • 1970-01-01
      • 2011-01-11
      • 2019-08-20
      相关资源
      最近更新 更多