简体   繁体   中英

How to convert a list to list of lists by group the elements when an element repeats in Scala idiomatic way

I want to convert a list of elements into a list of lists by breaking whenever an element repeats like below

Input:

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

Ouput:

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

Here's what I've tried:

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

I'm looking for a better way to achieve this using map/foldLeft... functions without using ListBuffer.

Thanks in advance.

Here is a version using 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

Using drop and take ensures that this works on an empty list. Building the result in reverse means that the latest List is always at the head, and will also be efficient for long lists.

And for completeness, here is a recursive function that does the same thing:

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)
}

Using the solution provided for below question, I have come up with the below solution

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)
}

Here is a very direct solution which uses foldLeft while keeping track of two accumulator arrays: One for the final result, and one for the currently considered token sublist. Finally, we combine the result array with the last sublist.

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

Notice that this is not the computationally most efficient solution, since for each iteration we are checking through the entire sublist under consideration using contains . If you wish for efficiency, you might (depending on your data) consider an approach which uses hash maps or similar data structures instead for faster repetition checks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM