【问题标题】:Merge List[List[_]] conditionally有条件地合并列表[List[_]]
【发布时间】:2018-03-21 15:10:19
【问题描述】:

我想根据内部列表中元素的值合并List[List[Double]]。到目前为止,这是我所拥有的:

// inner Lists are (timestamp, ID, measurement)
val data = List(List(60, 0, 3.4),  List(60, 1, 2.5),  List(120, 0, 1.1),
                List(180, 0, 5.6), List(180, 1, 4.4), List(180, 2, 6.7))

data
  .foldLeft(List[List[Double]]())(
    (ret, ll) =>

      // if this is the first list, just add it to the return val
      if (ret.isEmpty){
        List(ll)

      // if the timestamps match, add a new (ID, measurement) pair to this inner list
      } else if (ret(0)(0) == ll(0)){
        {{ret(0) :+ ll(1)} :+ ll(2)} :: ret.drop(1)

      // if this is a new timestamp, add it to the beginning of the return val
      } else {
        ll :: ret
      }
  )

这行得通,但它对我来说并不是最佳选择(尤其是右侧添加“:+”)。对于我的用例,我有一个相当大的(约 25,000 个内部列表)元素列表,它们本身都是长度为 3 的列表。最多会有四重退化,因为内部列表是List(timestamp, ID, measurement) 组,并且只有四个唯一ID。本质上,我想将所有具有相同时间戳的测量值混合在一起。

有没有人看到更优化的方法?

如果有更好的方法从那一点开始的话,我实际上是从时间戳List[Double] 和四个 ID 中的每一个的测量值 List[Double] 开始的。

【问题讨论】:

  • 我不太明白。你到底想达到什么目的?您“想要合并”并且“想要弄脏”,并且在整个代码中散布了一些精美的 cmets,但是仍然不清楚最终目标是什么。最后你真的想要(time, id, meas, id, meas, id, meas, ...)-lists 吗?为什么你想把它做成这么尴尬的格式,为什么不把它正确地建模成Map[Timestamp, Map[Id, Measurement]] 之类的?
  • 是的,我想在最后列出(time, id, meas, id, meas, id, meas, ...)。这样,它可以很容易地写入 CSV,并在必要时重新解析。
  • 如果您的输入时间戳有问题会怎样?
  • @Tom 他们不是,因为我在做任何这些之前都会对列表进行排序
  • 在这种情况下,我会使用下面提供的groupBy 解决方案,您不需要事先进行排序,如果您的列表很大,这将有所帮助。

标签: scala list functional-programming stream


【解决方案1】:

这里有一个稍微短一点的方法:

data.
  groupBy(_(0)).
  mapValues(_.flatMap(_.tail)).
  toList.
  map(kv => kv._1 :: kv._2)

结果看起来与您的算法产生的结果 1:1 完全相同:

List(List(180.0, 0.0, 5.6, 1.0, 4.4, 2.0, 6.7), List(120.0, 0.0, 1.1), List(60.0, 0.0, 3.4, 1.0, 2.5))
List(List(180.0, 0.0, 5.6, 1.0, 4.4, 2.0, 6.7), List(120.0, 0.0, 1.1), List(60.0, 0.0, 3.4, 1.0, 2.5))

解释:

  • 按时间戳分组
  • 在分组值中,删除多余的时间戳,并展平为单个列表
  • 将时间戳重新添加到 ids-&-measurements 的平面列表中

【讨论】:

  • _.map(_.tail).flatten 可以简写为_.flatMap(_.tail)
【解决方案2】:

这是一种可能性:

input
  .groupBy(_(0))
  .map { case (tstp, values) => tstp :: values.flatMap(_.tail) }

这个想法只是将内部列表按其第一个元素分组,然后将结果值展平。

返回:

List(List(180.0, 0.0, 5.6, 1.0, 4.4, 2.0, 6.7), List(120.0, 0.0, 1.1), List(60.0, 0.0, 3.4, 1.0, 2.5))

【讨论】:

  • 有一个漂亮的方法叫做tail,它的作用和drop(1)完全一样。
  • 最后需要一个toList,不是吗? REPL 告诉我这是一个不可变的。可迭代的,而不是列表。
  • groupBy 不保留键的顺序,因为它返回 Map。这将是两种算法之间的唯一区别。
  • @awwsmm 实际上它返回了一个 Iterable,可以使用 toList 将其转换为 List。
  • @Miguel groupBy期间订单丢失。
【解决方案3】:

用案例类表示您的测量结果怎么样?

case class Measurement(timestamp: Int, id: Int, value: Double)

val measurementData = List(Measurement(60, 0, 3.4),  Measurement(60, 1, 2.5),  
    Measurement(120, 0, 1.1), Measurement(180, 0, 5.6), 
    Measurement(180, 1, 4.4), Measurement(180, 2, 6.7))

measurementData.foldLeft(List[Measurement]())({
    case (Nil, m) => List(m)
    case (x :: xs, m) if x.timestamp == m.timestamp => m :: xs
    case (xs, m) => m :: xs
  })

【讨论】:

  • 好主意,但对于我想做的事情来说有点太投入了。无论如何,谢谢!
  • 我通常会建议这样做。但由于都是 CSV 输出,因此 CSV 格式不关心案例类,不幸的是......
猜你喜欢
  • 2012-09-07
  • 2014-01-26
  • 2016-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-06
  • 2017-08-02
  • 1970-01-01
相关资源
最近更新 更多