是否有一个函数式编程习惯用于“从列表的开头选择并减少直到结果满足谓词”?

时间:2016-01-04 21:46:33

标签: functional-programming language-agnostic idioms

假设我有一个数字列表,我需要知道从一开始就要选择多少元素以获得至少所需的总和。

算法很简单:我从列表的开头选择数字,直到所有选中数字的总和超过一定数量。

我可以用这样的命令式写作:

fun pickEnough(list: List<Double>, enough: Double): List<Double>? {
    var soFar = 0.0
    var index = 0
    for (index in 0..list.size) {
        soFar += list[index]
        if (soFar > enough) {
            return list.subList(0, index)
        }
    }
    return null
}

一个低效但更通用的解决方案是生成所有可能的子列表并选择第一个减少结果足够好的子列表:

fun <T> pickEnough(list: List<T>, reducer: (T, T) -> T, enough: (T) -> Boolean): List<T>? =
list.indices
    .map { index -> list.sublist(0, index) }
    .first { sublist -> enough(sublist.reduce(reducer)) }

pickEnough(listOf(5,8,0,0,8), { a, b -> a + b}, { it > 10 }) // [5, 8]

是否存在既定的功能习惯用语,或者可能是性能和表现力的组合,而不是我试图推广这篇文章?

这个例子在Kotlin中,但我更喜欢与语言无关的答案,尽管任何语言的答案都是值得赞赏的,只要它们提供描述此操作的高级习语。

4 个答案:

答案 0 :(得分:6)

您想要的是scan后跟takeWhilescan就像一个折叠,除了它返回一系列连续的状态值。您可以返回一对(x, soFar)的连续状态,其中包含序列中的当前值和当前运行总计。然后,您可以从此序列中获取尽可能多的数据,其中当前值未导致超出所需总数。例如在F#中你可以这样做:

let pickEnough (l: seq<double>) (enough: double): seq<double> =
    Seq.scan (fun (_, soFar) x -> (x, x+soFar)) (0.0, 0.0) l |> 
    Seq.skip 1 |> 
    Seq.takeWhile (fun (x, soFar) -> soFar - x < enough) |> 
    Seq.map fst

答案 1 :(得分:4)

这是我的Kotlin版Lee的答案:

fun <A, B> Iterable<A>.scanl(initial: B, f: (B, A) -> B): List<B> {
   return listOf(initial).plus(if (this.count() == 0) {
       emptyList()
   } else {
       this.drop(1).scanl(f(initial, this.first()), f)
   })
}

fun pickEnough(list: List<Int>, enough: Int): List<Int>? {
    return list
      .scanl(0 to 0) {
        pair, x ->
        x to (x + pair.second)
      }
      .drop(1)
      .takeWhile {
        pair ->
        val (x, soFar) = pair
        soFar - x < enough
      }
      .map { it.first }
}

I put my code with some tests on gist.

答案 2 :(得分:2)

我用这个:

breaks=fhat[["cont"]]["5%"]

答案 3 :(得分:0)

在Clojure中有一个reduced函数:

;; Clojure
(reduce (fn [[sum list] el]
            (if (< 10 sum)
              (reduced list)
              [(+ sum el) (conj list el)]))
          [0 []]
          [5 8 0 0 8]) ;; => [5 8]

如果没有指定列表不够大的返回内容,则在这种情况下它将返回一个向量:[sum-of-the-array original-array]

当然可以很容易地改变。