【问题标题】:How to combine a subset of the list of pairs and return the index of the last element如何组合对列表的子集并返回最后一个元素的索引
【发布时间】:2014-02-26 00:00:22
【问题描述】:

假设我有一个按第一个元素排序的对列表,看起来像这样:

List((0.0,0.001), (0.001,1.0), (1.0,2.0), (2.0,3.0), (3.0,4.0), (4.0,5.0), (5.0,15.0), (15.0,30.0), (30.0,45.0), (45.0,60.0), (60.0,71.0))

这本质上是一个区间列表。

给定一个起始元素,例如对应于(2.0,3.0) 的元素3,以及一个区间长度,例如40,如果我从索引开始,我想找到40 结束的区间索引3.

所以本质上,对于从(在这种情况下)元素 3 开始的每个元素,我需要执行 _._2 - _._1 并将它们相加,直到长度超过 40。一旦我到达它,我需要停止。我不想map整个列表到持续时间(列表可能很长),无论如何我不想foldLeft所有这些,我想在达到所需的持续时间后立即停止.它类似于传统命令式for 循环的break

如果您要使用普通的旧 Java 命令式地执行此操作,这将非常简单。有没有什么聪明的技巧可以在 Scala 中以函数式的方式做到这一点?

【问题讨论】:

  • 我鼓励你为间隔类型创建一个真正的抽象。元组是弱抽象,例如映射、数组和集合等等。
  • 好吧,我可以创建一个Interval 案例类,它提供一个计算持续时间的val。我还可以使用仅包含每对的第一个数字的更简单列表,因为第二个数字与下一对的第一个数字相同(在我的情况下我没有空白),但基本上我所拥有的信息没有改变那么多,我的问题是如何foldLeft(或以某种方式累积)列表的一部分,直到我达到某个标准,并且知道遍历了多少元素才能达到它

标签: scala map fold


【解决方案1】:

听起来scanLeft 正是您要查找的内容——它可以让您计算运行总数:

def findIt(intervals: Seq[(Double, Double)], value: Double, start: Int): Int =
  start + intervals.drop(start).scanLeft(0.0) {
    case (acc, (b, e)) => acc + e - b
  }.tail.indexWhere(_ > value)

或者,可能更清楚:

def intervalLength(interval: (Double, Double)) = interval._2 - interval._1

def findIt(intervals: Seq[(Double, Double)], value: Double, start: Int): Int =
  start + intervals
            .drop(start)
            .map(intervalLength)
            .scanLeft(0.0)(_ + _).tail.indexWhere(_ > value)

在您的情况下,此方法将计算整个列表的总数,但您可以通过将输入转换为 Stream 轻松使其变得懒惰。为了证明这是可行的,我们可以编写以下内容,其中xs 是您的示例列表:

scala> findIt(Stream.continually(xs).flatten, 40.0, 3)
res0: Int = 8

我们刚刚在无限流上运行findIt,这是因为永远重复您的数据,我们会立即得到结果,因此我们绝对不会计算所有总数。

【讨论】:

  • SO 应该有一种模式,就像在学术界一样,由所谓的主要作者辅导一个倒霉的博士后做出贡献;并且每个人都获得了荣誉。每个人都受益。我不知道你在哪里花费 SO 积分。
【解决方案2】:
def indexOf(intervals: List[(Double, Double)], start: Int, interval: Double): Option[Int] = {
  def recur(el: List[(Double, Double)], pos: Int, remaining: Double): Option[Int] = {
    def duration(p: (Double, Double)): Double = p._2 - p._1
    el match {
      case head :: _ if remaining - duration(head) < 0 => Some(pos)
      case head :: _ => recur(el.tail, pos+1, remaining - duration(head))
      case Nil => None
    }
  }
  recur(intervals.drop(start), start, interval)
}

示例用法:

val intervals = List((0.0,0.001), (0.001,1.0), (1.0,2.0), (2.0,3.0), (3.0,4.0), (4.0,5.0), (5.0,15.0), (15.0,30.0), (30.0,45.0), (45.0,60.0), (60.0,71.0))
println(indexOf(intervals, 0, 0.0005))
println(indexOf(intervals, 0, 0.001))
println(indexOf(intervals, 3, 40d))
println(indexOf(intervals, 3, 200d))

输出:

Some(0)
Some(1)
Some(8)
None

【讨论】:

  • 当有一个很好的组合器可以更清楚地完成工作时,为什么要使用显式递归?
  • 我认为向提问者展示另一种方法很有用,因为他们不太熟悉 Scala。我会这么说,尽管我很喜欢你的回答,但我不同意只有在无法将集合调用链接在一起以获得相同答案时才允许递归的暗示。
  • 很公平——我绝对不想建议显式递归“不应该被允许”在任何意义上,但我确实认为将问题放入一些递归组合器的约束中(折叠、扫描、映射)是编写更好的 Scala 代码的最佳方法之一。
  • 不会对此争论。干杯。
猜你喜欢
  • 2017-05-11
  • 2020-02-19
  • 2018-07-09
  • 2012-10-10
  • 2023-04-02
  • 2016-01-30
  • 2018-08-16
  • 2021-09-08
  • 1970-01-01
相关资源
最近更新 更多