计算列表中满足给定谓词的元素数

mis*_*tor 39 haskell functional-programming

Haskell标准库是否具有给定列表和谓词的函数,返回满足该谓词的元素数量?类似于类型的东西(a -> Bool) -> [a] -> Int.我的搜索没有回复任何有趣的东西.目前我正在使用length . filter pred,我发现这并不是一个特别优雅的解决方案.我的用例似乎很常见,有一个更好的库解决方案.是这种情况还是我的预感错了?

Lou*_*man 41

length . filter p执行不按照你的建议几乎一样糟糕.特别是,它在内存和速度方面只有不断的开销,所以是的.

对于使用流融合的东西,如vector包,length . filter p将实际上进行优化,以避免创建中间向量.然而,列表使用目前所谓的foldr/build融合,这不够智能,length . filter p无法创建线性大的风险堆栈溢出.

有关流融合的详细信息,请参阅此文章.据我了解,目前在主Haskell库中没有使用流融合的原因是(如本文所述),当基于流的库实现时,大约5%的程序执行得更糟,而foldr/build优化永远不会(AFAIK)使表现更加恶化.

  • 这实际上是关于Haskell最酷的事情之一:您可以在自己的代码中指定重写规则,优化器使用它们,并使用它们来获得非常好的可组合和高效的代码. (4认同)
  • 这也有效......但是建议一句:如果你在`count`的声明之上使用`{ - #INLINE count# - }`pragma,你会得到更好的结果.("更好的结果"指的是"优化者获得更多机会去做它的事情.") (3认同)
  • @LouisWasserman:融合发生在"好消费者"消耗"好生产者"的结果时.两者都列在[这里](http://www.haskell.org/ghc/docs/7.2.2/html/users_guide/rewrite-rules.html#id570014),并且不包括`length`.另见:[GHC#876:长度不是一个好消费者](http://hackage.haskell.org/trac/ghc/ticket/876). (3认同)
  • 我要加上`count pred = length.过滤pred`到我的utils. (2认同)

ehi*_*ird 6

不,没有预定义的功能可以做到这一点,但我想说的length . filter pred是,事实上,这是一个优雅的实现; 它就像你能够直接调用概念一样表达你的意思,如果你定义它就不能做到.

唯一的选择是递归功能或折叠,IMO不那么优雅,但如果你真的想:

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

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


Mat*_*hid 5

Haskell是一种高级语言.它不是为您可能遇到的每种可能情况组合提供一种功能,而是为您提供涵盖所有基础知识的一小组功能,然后根据需要将这些功能粘合在一起以解决当前存在的任何问题.

在简洁和简洁方面,这是优雅的.所以,是的,length . filter pred绝对标准的解决方案.作为另一个例子,考虑一下elem(如您所知),它会告诉您列表中是否存在给定项目.实际上,标准参考实现是这样的

elem :: Eq x => x -> [x] -> Bool
elem x = foldr (||) False . map (x ==)

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

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

  1. 编译器通常可以优化由这样的代码创建的临时数据结构.(请记住,这是在Haskell中编写代码的标准方法,因此编译器已经过调整以处理它.)

  2. 即使它无法被优化掉,懒惰通常也会使这些代码相当有效.

(在这个具体的例子中,OR函数会在看到匹配时立即终止循环 - 就像你自己手动编码一样会发生这种情况.)

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

  • `涵盖所有基础知识的一小部分函数,​​然后根据需要将它们粘合在一起以解决当前手头的任何问题`在我的书中这是低级别的定义 (2认同)