【问题标题】:How to convert a list to list of lists by group the elements when an element repeats in Scala idiomatic way当元素以Scala惯用方式重复时,如何通过对元素进行分组来将列表转换为列表列表
【发布时间】:2019-03-31 15:58:15
【问题描述】:

我想将元素列表转换为列表列表,只要一个元素像下面这样重复就中断

输入:

List(1, 2, 3, 1, 2, 1, 3, 1, 2, 3)

输出:

List[List[Integer]] = List(List(1, 2, 3), List(1, 2), List(1, 3), List(1, 2, 3))

这是我尝试过的:

val tokens = List(1,2,3,1,2,1,3,1,2,3)

val group = new ListBuffer[List[Integer]]()
val nextGroup = new ListBuffer[Integer]()
val nextTokens = new ListBuffer[Integer]()

for (t <- tokens) {
  if (nextTokens.contains(t)) {
    group += nextGroup.toList
    nextGroup.clear()
    nextTokens.clear()
  }
  nextGroup += t
  nextTokens += t
}

group += nextGroup.toList

group.toList

我正在寻找一种更好的方法来实现这一点,使用 map/foldLeft... 函数而不使用 ListBuffer。

提前致谢。

【问题讨论】:

  • 所以只要有 1,您就将列表分开?
  • 就我而言,元素 1 不固定。每当遇到重复的元素时,我都想打破列表。
  • @jrook 这个问题不是你发布的问题的重复,因为在我的场景中没有固定的分隔符。
  • 感谢@jrook 参考类似问题有助于提出解决方案

标签: scala list functional-programming


【解决方案1】:

这是一个使用foldLeft的版本

tokens
  .drop(1)
  .foldLeft(List(tokens.take(1))) { case (res, token) =>
    if (res.head.contains(token)) {
      List(token) +: res
    } else {
      (res.head :+ token) +: res.tail
    }
  }
  .reverse

使用droptake 可确保这适用于空列表。反向构建结果意味着最新的List 始终位于最前面,并且对于长列表也很有效。

为了完整起见,这里有一个做同样事情的递归函数:

def groupDistinct[T](tokens: List[T]): List[List[T]] = {
  @tailrec
  def loop(token: List[T], cur: List[T], res: List[List[T]]): List[List[T]] =
    token match {
      case Nil =>
        res :+ cur
      case head :: tail =>
        if (cur.contains(head)) {
          loop(tail, List(head), res :+ cur)
        } else {
          loop(tail, cur :+ head, res)
        }
    }

  loop(tokens, Nil, Nil)
}

【讨论】:

    【解决方案2】:

    使用为以下问题提供的解决方案,我想出了以下解决方案

    https://stackoverflow.com/a/52976957/1696418

    mySeq.foldLeft(List.empty[List[Int]]) {
      case (acc, i) if acc.isEmpty => List(List(i))
      case (acc, i) if acc.last.contains(i) => acc :+ List(i)
      case (acc, i) => acc.init :+ (acc.last :+ i)
    }
    

    【讨论】:

    • 如果列表很长,这将表现不佳,因为lastinit 需要搜索整个列表,并且您每次都检查isEmpty,即使它只是一个问题对于第一个元素。
    【解决方案3】:

    这是一个非常直接的解决方案,它使用foldLeft 同时跟踪两个累加器数组:一个用于最终结果,一个用于当前考虑的令牌子列表。最后,我们将结果数组与最后一个子列表结合起来。

    val (acc, last) = tokens.foldLeft ((List[List[Int]](), List[Int]())) ((a,b) =>
                if (a._2.contains(b)) (a._1 :+ a._2, List(b))
                else (a._1, a._2 :+ b))
    acc :+ last
    

    请注意,这不是计算效率最高的解决方案,因为对于每次迭代,我们都在使用 contains 检查整​​个考虑中的子列表。如果您希望提高效率,您可以(取决于您的数据)考虑一种使用哈希映射或类似数据结构的方法,而不是更快地进行重复检查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-05
      • 2015-02-13
      • 1970-01-01
      相关资源
      最近更新 更多