我有一个昂贵的函数,我希望根据以下要求尽可能少地运行几次:
我无法使用Iterator的takeWhile / dropWhile找到一个好的解决方案,因为我想包含第一个匹配元素。刚刚得到以下解决方案:
val pseudoResult = Map("a" -> 0.6,"b" -> 0.2, "c" -> 1.0)
def expensiveFunc(s:String) : Double = {
pseudoResult(s)
}
val inputsToTry = Seq("a","b","c")
val inputIt = inputsToTry.iterator
val results = mutable.ArrayBuffer.empty[(String, Double)]
val earlyAbort = 0.5 // threshold
breakable {
while (inputIt.hasNext) {
val name = inputIt.next()
val res = expensiveFunc(name)
results += Tuple2(name,res)
if (res<earlyAbort) break()
}
}
println(results) // ArrayBuffer((a,0.6), (b,0.2))
val (name, bestResult) = results.minBy(_._2) // (b, 0.2)
如果我设置了val earlyAbort = 0.1
,结果应该仍然是(b, 0.2)
,而无需再次评估所有情况。
答案 0 :(得分:3)
您可以利用Stream
来实现所需的功能,请记住Stream
是一种惰性集合,可以按需评估操作。
这是scala Stream文档。
您只需要这样做:
val pseudoResult = Map("a" -> 0.6,"b" -> 0.2, "c" -> 1.0)
val earlyAbort = 0.5
def expensiveFunc(s: String): Double = {
println(s"Evaluating for $s")
pseudoResult(s)
}
val inputsToTry = Seq("a","b","c")
val results = inputsToTry.toStream.map(input => input -> expensiveFunc(input))
val finalResult = results.find { case (k, res) => res < earlyAbort }.getOrElse(results.minBy(_._2))
如果find
没有得到任何值,则可以使用相同的流来找到最小值,并且该函数不会再次求值,这是因为存在备注:
Stream类还使用了备注,以便将先前计算的值从Stream元素转换为类型A的具体值
请考虑一下,如果原始集合为空,则此代码将失败,如果要支持空集合,则应将minBy
的{{1}}和sortBy(_._2).headOption
替换为getOrElse
:
orElse
其输出为:
评估一个
求b
finalResult:(String,Double)=(b,0.2)
finalResultOpt:选项[(String,Double)] = Some((b,0.2))
答案 1 :(得分:1)
要做的最清楚,最简单的操作是在输入上fold
,仅传递当前的最佳结果。
val inputIt :Iterator[String] = inputsToTry.iterator
val earlyAbort = 0.5 // threshold
inputIt.foldLeft(("",Double.MaxValue)){ case (low,name) =>
if (low._2 < earlyAbort) low
else Seq(low, (name, expensiveFunc(name))).minBy(_._2)
}
//res0: (String, Double) = (b,0.2)
它仅根据需要多次调用expensiveFunc()
,但它确实遍历了整个输入迭代器。如果那仍然很麻烦(很多输入),那么我将采用尾递归方法。
val inputIt :Iterator[String] = inputsToTry.iterator
val earlyAbort = 0.5 // threshold
def bestMin(low :(String,Double) = ("",Double.MaxValue)) :(String,Double) = {
if (inputIt.hasNext) {
val name = inputIt.next()
val res = expensiveFunc(name)
if (res < earlyAbort) (name, res)
else if (res < low._2) bestMin((name,res))
else bestMin(low)
} else low
}
bestMin() //res0: (String, Double) = (b,0.2)
答案 2 :(得分:0)
在输入列表中使用视图: 尝试以下操作:
val pseudoResult = Map("a" -> 0.6, "b" -> 0.2, "c" -> 1.0)
def expensiveFunc(s: String): Double = {
println(s"executed for ${s}")
pseudoResult(s)
}
val inputsToTry = Seq("a", "b", "c")
val earlyAbort = 0.5 // threshold
def doIt(): List[(String, Double)] = {
inputsToTry.foldLeft(List[(String, Double)]()) {
case (n, name) =>
val res = expensiveFunc(name)
if(res < earlyAbort) {
return n++List((name, res))
}
n++List((name, res))
}
}
val (name, bestResult) = doIt().minBy(_._2)
println(name)
println(bestResult)
输出:
executed for a
executed for b
b
0.2
如您所见,仅对a和b求值,而不对c求值。
答案 3 :(得分:0)
这是尾递归的用例之一:
import scala.annotation.tailrec
val pseudoResult = Map("a" -> 0.6,"b" -> 0.2, "c" -> 1.0)
def expensiveFunc(s:String) : Double = {
pseudoResult(s)
}
val inputsToTry = Seq("a","b","c")
val earlyAbort = 0.5 // threshold
@tailrec
def f(s: Seq[String], result: Map[String, Double] = Map()): Map[String, Double] = s match {
case Nil => result
case h::t =>
val expensiveCalculation = expensiveFunc(h)
val intermediateResult = result + (h -> expensiveCalculation)
if(expensiveCalculation < earlyAbort) {
intermediateResult
} else {
f(t, intermediateResult)
}
}
val result = f(inputsToTry)
println(result) // Map(a -> 0.6, b -> 0.2)
val (name, bestResult) = f(inputsToTry).minBy(_._2) // ("b", 0.2)
答案 4 :(得分:0)
如果实现takeUntil
并使用它,那么即使您找不到要查找的内容,仍然必须再次遍历该列表以获取最低的列表。可能会有更好的方法,那就是将find
与reduceOption
组合在一起的函数,如果发现某些东西,则尽早返回,否则返回将集合简化为单个项的结果(在您的情况下,找到最小的)。
结果与使用Stream
可以达到的效果相当,如上一个答复中突出显示的那样,但是避免了利用记忆,因为记忆非常庞大。
可能的实现方式如下:
import scala.annotation.tailrec
def findOrElse[A](it: Iterator[A])(predicate: A => Boolean,
orElse: (A, A) => A): Option[A] = {
@tailrec
def loop(elseValue: Option[A]): Option[A] = {
if (!it.hasNext) elseValue
else {
val next = it.next()
if (predicate(next)) Some(next)
else loop(Option(elseValue.fold(next)(orElse(_, next))))
}
}
loop(None)
}
让我们添加输入以对此进行测试:
def f1(in: String): Double = {
println("calling f1")
Map("a" -> 0.6, "b" -> 0.2, "c" -> 1.0, "d" -> 0.8)(in)
}
def f2(in: String): Double = {
println("calling f2")
Map("a" -> 0.7, "b" -> 0.6, "c" -> 1.0, "d" -> 0.8)(in)
}
val inputs = Seq("a", "b", "c", "d")
以及我们的助手:
def apply[IN, OUT](in: IN, f: IN => OUT): (IN, OUT) =
in -> f(in)
def threshold[A](a: (A, Double)): Boolean =
a._2 < 0.5
def compare[A](a: (A, Double), b: (A, Double)): (A, Double) =
if (a._2 < b._2) a else b
我们现在可以运行它,看看它如何运行:
val r1 = findOrElse(inputs.iterator.map(apply(_, f1)))(threshold, compare)
val r2 = findOrElse(inputs.iterator.map(apply(_, f2)))(threshold, compare)
val r3 = findOrElse(Map.empty[String, Double].iterator)(threshold, compare)
r1
为Some(b, 0.2)
,r2
为Some(b, 0.6)
,而r3
为None
。在第一种情况下,由于我们使用了惰性迭代器并提早终止,因此我们仅调用两次f1
。
您可以查看结果,并可以使用此代码here on Scastie。