首页 文章

有条件地合并序列中的项目

提问于
浏览
2

我有一个序列列表 val as = Seq[A] . A 看起来像这样:

import java.time.Instant
case class A(t: String, start: Instant, end: Instant)

现在我想有条件地合并 as 中的元素:每当两个后续的,即直接相邻的项 a1a2 具有相同的 t 值时,它们应该合并为如下:

object A {
  def merge(a1: A, a2: A): A = {
    require(a1.t == a2.t)
    A(a1.t, a1.start, a2.end)
  }
}

请注意,永远不会在另一个项目上直接成功的项目不应合并,例如:

Seq(A("a", ...), A("a", ...), A("b", ...), ...) // -> merge the first two elements

然而:

Seq(A("a", ...), A("b", ...), A("a", ...), ...) // -> do not merge any elements

有一个similar question on SO,但它合并了多个列表,所以它不适用于我的情况 .

我的第一个方法:

as.zip(as.tail)
  .map { 
    case (a1: A, a2: A) if (a1.t == a2.t) => merge(a1, a2)
    case (a1: A, a2: A) => ???
  }

但是有很多缺陷 . 除此之外,我不确定在第二种情况下该怎么做(即同时保持 a1a2 ),这对于应合并的多个后续元素也不起作用 .

我的直觉使我更加注重 foldLeft

as.foldLeft(???)(A.merge)

这解决了需要合并的多个后续元素的问题 . 但是,它会尝试合并我的 merge 实现无法实现的所有元素 .

我可以适应 merge() ,但我仍然不清楚如何:如果 a1.t == a2.t ,结果类型应该是新的 A ,否则它应该是'return' a1a2 .

我对后一种想法的处理方法是将这些方法添加到类 A 中:

def merge(that: A): (A, Option[A]) =
  if (this.t == that.t)
    (A(t, this.start, that.end), None)
  else
    (this, Some(that))

但是在这里,我无法在序列 asfoldLeft() 调用中使用 merge() 的输出 .

根本问题在于两种方法:我应该如何处理有时(当 t 匹配时),我想在新序列中添加一个新的 A ,而在其他情况下,我需要添加两个元素 .

我对如何以函数式编程方式解决这个问题感到有点迷茫 . 我当然可以迭代 as 列表,存储应该在新数据结构中合并的那些,并再次生成它 . 有没有更好的办法?

2 回答

  • 0

    基于RoberMP的答案,如果你使用 foldRight 来构造 List ,你可以使用与cons运算符匹配的模式,这应该比tail运算更有效:

    def merge(sequence: Seq[A]) = {
      sequence.foldRight(List.empty[A]) {
        case (A(t1, x, _), A(t2, _, y) :: list) if t1 == t2 => A(t1, x, y) :: list
        case (other, list) => other :: list
      }
    }
    

    这是从右到左工作,以便我们可以在匹配中使用 :: 但仍应具有预期的输出 .

  • 3

    我认为foldLeft是正确的方法,类似于(未经测试):

    as.foldLeft(Seq.empty[A])(merge)
    
    def merge(acc: Seq[A], a: A): Seq[A] = {
      if(acc.isEmpty) Seq(a)
      else if(acc.last.t == a.t) acc.init :+ A(a.t, acc.last.start, a.end)
      else acc :+ a
    }
    

    请注意,这里有很多尾部操作,所以不知道Seq是否是这个用例的更好的集合

相关问题