【问题标题】:Computing all values or stopping and returning just the best value if found计算所有值或停止并仅返回最佳值(如果找到)
【发布时间】:2013-11-21 10:41:24
【问题描述】:

我有一个项目列表,我为每个项目计算一个值。计算这个值有点计算密集,所以我想尽可能地减少它。

我需要实现的算法是这样的:

  1. 我有一个值 X

  2. 每个项目

    一个。计算它的值,如果它是

    b. if (值 > 0) && (值

  3. 返回 List 中的所有 (item, value) 对(值 > 0),理想情况下按值排序

为了更清楚一点,第 3 步仅在没有任何项的值小于 X 的情况下发生。在第 2 步中,当我们遇到第一个小于 X 的项时,我们不应该计算其余项并返回该项目(我们显然可以在 Set() 中单独返回它以匹配返回类型)。

我目前的代码如下:

  val itemValMap = items.foldLeft(Map[Item, Int)]()) {
  (map : Map[Item, Int], key : Item) =>
    val value = computeValue(item)
    if ( value >= 0 )        //we filter out negative ones
      map + (key -> value)
    else
      map
  }

 val bestItem = itemValMap.minBy(_._2)
 if (bestItem._2 < bestX)
 {
      List(bestItem)
 }
 else
 {
    itemValMap.toList.sortBy(_._2)
 }

但是,此代码所做的是计算列表中的所有值并选择最佳值,而不是在找到“更好”的值时停止。我怀疑我必须以某种方式使用 Streams 来实现这一点?

【问题讨论】:

  • Patryk Ćwiek 和 Régis Jean-Gilles 的两种解决方案都运行良好。出于某种原因,在我的场景中,选择的答案表现得更好一些,但这可能取决于我对程序其余部分的实现,或者Stream 如何发挥其内部魔力(我没有单独测试两者) .不幸的是,我只能选择一个答案。如果您正在寻找类似的解决方案,请尝试两者,看看哪种方案最适合您的方案。

标签: list scala map stream


【解决方案1】:

好的,我不确定您的整个设置如何,但我尝试准备一个最小的示例来反映您的情况。

那就是:

object StreamTest {
  case class Item(value : Int)
  def createItems() = List(Item(0),Item(3),Item(30),Item(8),Item(8),Item(4),Item(54),Item(-1),Item(23),Item(131))
  def computeValue(i : Item) = { Thread.sleep(3000); i.value * 2 - 2 }

  def process(minValue : Int)(items : Seq[Item]) = {
    val stream = Stream(items: _*).map(item => item -> computeValue(item)).filter(tuple => tuple._2 >= 0)
    stream.find(tuple => tuple._2 < minValue).map(List(_)).getOrElse(stream.sortBy(_._2).toList)
  }
}

每次计算需要 3 秒。现在让我们看看它是如何工作的:

val items = StreamTest.createItems()
val result = StreamTest.process(2)(items)
result.foreach(r => println("Original: " + r._1 + " , calculated: " + r._2))

给予:

[info] Running Main 
Original: Item(3) , calculated: 4
Original: Item(4) , calculated: 6
Original: Item(8) , calculated: 14
Original: Item(8) , calculated: 14
Original: Item(23) , calculated: 44
Original: Item(30) , calculated: 58
Original: Item(54) , calculated: 106
Original: Item(131) , calculated: 260
[success] Total time: 31 s, completed 2013-11-21 15:57:54

由于没有小于 2 的值,我们得到了一个按计算值排序的列表。请注意,缺少两对,因为计算的值小于 0 并被过滤掉了。

好的,现在让我们尝试使用不同的最小截止点:

val result = StreamTest.process(5)(items)

这给出了:

[info] Running Main 
Original: Item(3) , calculated: 4
[success] Total time: 7 s, completed 2013-11-21 15:55:20

很好,它返回了一个只有一个项目的列表,第一个值(原始列表中的第二个项目)小于“最小”值并且不小于 0。

我希望上面的例子很容易适应你的需要......

【讨论】:

  • 非常感谢您的回复。您介意多解释一下process 函数的第一行和第二行实际发生的情况吗?我只是想确保我不会创建包含值 foldLeft 而不是filter 的原因,以便尽快删除该项目),流无论如何要避免这种情况?此外,find 是否懒惰地工作,因为它一旦找到它就会停止,但地图中的实际 computeValue 仅在流获取下一个项目时计算?
  • @jbx 好吧,Stream 被设计为惰性(和缓存,并且可能是无限的),所以它应该 一个接一个地处理元素而不是创建中间集合。关于你的第二个问题 - 是的,这就是为什么我的第二次运行只用了 ~7 秒(两次计算)而不是 ~32 秒(10 次计算,从最终结果中丢弃了两次),如果无论如何计算了所有条目,两次运行都会采取约 30 秒。
  • 非常感谢您的解释。我会试试看。
  • 成功了。它似乎也比 View 解决方案快一点,后者也有效。不知道为什么(或者它是否与我的实现有关)。我在您的答案中对您的代码做了一个小的编辑,因为如果它们小于 0(不等于 0),我会过滤掉它们。您可能想要检查输出是否仍然匹配或将值从 -10 更改为正值(并删除 0 项),以便它与我的问题匹配,以供将来可能查看您的答案的人使用。
  • @jbx 我很高兴它有效。我已更改答案中的数据和输出以匹配新条件。
【解决方案2】:

避免计算不需要的值的一种简单方法是使用view 方法使您的集合变得惰性:

val weigthedItems = items.view.map{ i => i -> computeValue(i) }.filter(_._2 >= 0 )
weigthedItems.find(_._2 < X).map(List(_)).getOrElse(weigthedItems.sortBy(_._2))

例如,这里是 REPL 中的一个测试:

scala> :paste
// Entering paste mode (ctrl-D to finish)

type Item = String
def computeValue( item: Item ): Int = {
  println("Computing " + item)
  item.toInt
}
val items = List[Item]("13", "1", "5", "-7", "12", "3", "-1", "15")
val X = 10
val weigthedItems = items.view.map{ i => i -> computeValue(i) }.filter(_._2 >= 0 )
weigthedItems.find(_._2 < X).map(List(_)).getOrElse(weigthedItems.sortBy(_._2))

// Exiting paste mode, now interpreting.

Computing 13
Computing 1
defined type alias Item
computeValue: (item: Item)Int
items: List[String] = List(13, 1, 5, -7, 12, 3, -1, 15)
X: Int = 10
weigthedItems: scala.collection.SeqView[(String, Int),Seq[_]] = SeqViewM(...)
res27: Seq[(String, Int)] = List((1,1))

如您所见,computeValue 仅被调用到第一个值 1)

【讨论】:

  • 感谢您的回复。在我的原始代码中,我使用foldLeft 而不是filter 来避免创建一个集合两次,一次是所有项目,第二次是过滤后的项目,因为我可能有很多项目是&lt; 0 ,在这两种情况下我都不需要。 view 是否对 filter 做同样的事情?你也能解释一下getOrElse 部分吗?不知道你为什么在weightedItems.sortBy(_._2) 中使用weightedItems,它没有从值= 0 的项目,并尽早消除它们。
  • 如果您总是想过滤掉负值,只需将对 filter 的调用移动到 weigthedItems 的定义中。我已经更新了我的答案来做到这一点。 getOrElse 是为了让您只获得 find 的结果(如果有),或者返回整个集合及其值(这正是 weigthedItems 已经是,所以我只是返回)。
  • 谢谢,所以只是为了确保我明白了。我们在地图之前做view 的事实让它变得懒惰? IE。它只会在find 开始查找项目时计算computeValue,因此即使filter 也会懒惰地取出不符合条件的项目(而不是使用过滤后的项目创建新地图)。
  • 谢谢,您和 Patryk Ćwiek 的其他解决方案都有效。由于某种原因,使用 Stream 有点快,不知道为什么。
  • 如果这确实更快,您可以保持基本相同的代码,只进行主要更改:将view 替换为toStream,将List(_) 替换为Stream(_)。这给出了:val weigthedItems = items.toStream.map{ i =&gt; i -&gt; computeValue(i) }.filter(_._2 &gt;= 0 ); weigthedItems.find(_._2 &lt; X).map(Stream(_)).getOrElse(weigthedItems.sortBy(_._2))
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-23
  • 1970-01-01
  • 1970-01-01
  • 2016-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多