【问题标题】:Lazy computation of items in list until required element is found延迟计算列表中的项目,直到找到所需的元素
【发布时间】:2016-02-07 13:00:43
【问题描述】:

我正在努力使这个要求尽可能高效,因为它是组合问题解决器的一部分,所以每一点都有助于实现宏伟的计划。

假设我有一个元素列表,在本例中称为转换。

val possibleTransitions : List[Transition] = List[...] //coming from somewhere

我想在每个转换上执行一个(有点昂贵的)计算,以获得另一个对象,在本例中称为State

我这样做的自然方式是使用for-comprehensionmap。前者对我来说更方便,因为我想过滤掉一些不相关的State对象,比如之前已经处理过的对象。

val newStates = for {
     transition <- possibleTransitions
     state <- computeExpensiveOperation(transition)
     if (confirmNewState(state))
} yield state

State 包含一个值,我们称之为value(),它表示该状态的某种吸引力。如果value() 非常低(有吸引力),我想丢弃列表的其余部分并使用它。由于possibleTransitions 可能是一个很长的列表(数千个),理想情况下,如果第一个State 对象已经拥有我想要的value(),我最好避免这样做computeExpensiveOperation

另一方面,如果我没有找到具有吸引力的value() 的任何项目,我想保留所有并将它们添加到另一个列表中。

val newPending = pending ++ newStates

我试图为此使用Stream,以避免在处理它们之前计算所有值。如果我使用 find() 并且我没有找到所需的项目,那么我将无法在流中获取项目(因为它使用一次)。

目前我唯一能看到的是在for-comprehension 中使用possibleItems.toStream() 并创建另一个集合,逐个遍历每个项目,直到找到该项目(并丢弃该集合)或没有(并将集合与所有项目一起使用)。

我错过了一些更聪明更有效的方法吗?

【问题讨论】:

    标签: list scala stream for-comprehension


    【解决方案1】:

    我会使用惰性视图并将它们转换为流以缓存中间结果,然后您可以获得所需的信息:

    val newStates = for {
         transition <- possibleTransitions.view
         state <- computeExpensiveOperation(transition)
         if (confirmNewState(state))
    } yield state
    val newStatesStream = newStates.toStream // cache results
    
    val attractive = newStatesStream.find(isAttractive(_))
    attractive match {
      case Some(a) => // do whatever
      case None => {
        val newPending = pending ++ newStatesSteam
      }
    }
    

    由于流是惰性的,它只会在找到val attractive 行中的第一个元素之前计算。如果没有有吸引力的元素,则将计算并缓存完整的流,并且将返回 None。

    在计算新的待处理元素时,我们可以将此流附加到待处理。 (顺便说一句:pending 应该是一个队列)

    【讨论】:

    • 好吧,takeWhile 会给你一个迭代器/视图,一旦条件不满足就会停止,所以你只会计算到这一点的元素。 toList 确实会计算所有这些元素。通常最好在某个点计算元素,而不是生成过于复杂的视图。但是,如果您有其他情况并希望继续查看该视图,则可以将其省略。您也可以将其转换为流,而不是延迟但在第一次计算结果时缓存结果。你只需要知道你到底想做什么。
    • 是的。您也可以随时将 println 调用放入您的函数或 for 循环中以查看实际评估的内容。这不是你应该在真实代码中做的事情,但对于确定一切是否像你想要的那样懒惰非常有用。
    • 嗯,好的,现在我明白了你的问题,但是 takeWhile 没有副作用,所以有吸引力的将只包含来自 newStates 的第一个元素。我会尝试对其进行编辑,以便它满足您的需求。
    • 哇,现在这是流的一些高级用法。那么当 filter() 发生时,第二个流newStatesStream 是否也被填充? (因为我不想对所有元素计算两次,一次是在我过滤时,一次是在我取所有其他元素时)。
    • 是的,流保留了它计算的所有元素,所以过滤器返回一个新的流(你实际上可以考虑再次将它变成一个视图或迭代器,但因为它只会包含一个元素,所以它并不重要)。 headOption 导致该流的第一个元素被计算,这是通过从 newStateStream 请求元素直到找到第一个通过过滤器的元素来完成的。所以 newStateStream 必须计算到该点的所有元素并保留它们。这很舒服,通过在视图/迭代器、流和列表之间进行转换,您可以控制何时计算某些内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-24
    • 2020-09-03
    • 1970-01-01
    • 1970-01-01
    • 2020-09-25
    相关资源
    最近更新 更多