简体   繁体   中英

Scala split list into multiple lists

I need to split a List into multiple list based on sum of size of an element. So this is what I wrote in to achieve this. I am sure there will be a better way to write same in Scala. Any help is appreciated.

here is my code...

case class cs(name: String, size: Int)
val cl: List[cs] = List(cs("abc1", 3), cs("abc2", 2), cs("abc3", 1), cs("abc4", 2), cs("abc5", 2), cs("abc6", 1), cs("abc7", 5))

def splitBy(l: List[cs], chunkSize: Int = 5): List[List[cs]] = {

  var s = 0
  var tcl: List[cs] = List[cs]()
  var mcl: List[List[cs]] = List[List[cs]]()

  l.foreach{ e => {
    s = s + e.size
    if (s > chunkSize) {
      mcl = tcl +: mcl
      s = e.size
      tcl = Nil
    }
    tcl = e +: tcl
    println("TSize: " + tcl.size + " Elem: " + e + "Sum: " + s)
  }
  }

  mcl

}

println(splitBy(cl,5))
  @tailrec
  def splitBy(l: List[cs], chunkSize: Int, innerAcc: List[cs] = List.empty[cs], outerAcc: List[List[cs]] = List.empty[List[cs]]): List[List[cs]] = (l, innerAcc) match {
    case (Nil, Nil) => outerAcc
    case (Nil, x) => x :: outerAcc
    case (x :: xs, a) =>
      if (x.size > chunkSize) {
        // Assumed we are ignoring anything > chunkSize
        splitBy(xs, chunkSize, a, outerAcc)
      }
      // You may want to pass sum forwards for efficiency rather than recalculate every time... 
      else if(x.size + a.map(_.size).sum > chunkSize) {
        splitBy(xs, chunkSize, List(x), a :: outerAcc)
      }
      else {
        splitBy(xs, chunkSize, x :: a, outerAcc)
      }
  }

Output

List(List(cs(abc7,5)), List(cs(abc6,1)), List(cs(abc5,2), cs(abc4,2), cs(abc3,1)), List(cs(abc2,2), cs(abc1,3)))

You could use a foldLeft , followed by a map , if ordering does not matter, you could get rid of the reverse s:

case class cs(name: String, size: Int)
val cl: List[cs] = List(cs("abc1", 3), cs("abc2", 2), cs("abc3", 1), cs("abc4", 2), cs("abc5", 2), cs("abc6", 1), cs("abc7", 5))

def splitBy(l: List[cs], chunkSize: Int = 5): List[List[cs]] =
  l.foldLeft(List[(List[cs],Int)]()) {
    case (accum @ (_, size: Int) :: _, next: cs) if size + next.size > chunkSize =>
      (next :: Nil, next.size) :: accum
    case ((chunk, size: Int) :: rest, next: cs) =>
      (next :: chunk, size + next.size) :: rest
    case (Nil, next: cs) =>
      (next :: Nil, next.size) :: Nil
  }.map {
    case (chunk, size: Int) =>
      chunk.reverse
  }.reverse

splitBy(cl, 5) foreach println

Output:

List(cs(abc1,3), cs(abc2,2))
List(cs(abc3,1), cs(abc4,2), cs(abc5,2))
List(cs(abc6,1))
List(cs(abc7,5))

I originally misinterpreted the question to mean you want chunks of at least chunkSize . This was my old response, which I'll leave here for posterity, using a scanLeft followed by collect :

case class cs(name: String, size: Int)
val cl: List[cs] = List(cs("abc1", 3), cs("abc2", 2), cs("abc3", 1), cs("abc4", 2), cs("abc5", 2), cs("abc6", 1), cs("abc7", 5))

def splitBy(l: List[cs], chunkSize: Int = 5): List[List[cs]] =
  l.scanLeft(List[cs]() -> 0) {
    case ((_, size: Int), next: cs) if size >= chunkSize =>
      (next :: Nil, next.size)
    case ((chunk, size: Int), next: cs) =>
      (next :: chunk, size + next.size)
  }.collect {
    case (chunk, size: Int) if size >= chunkSize =>
      chunk.reverse
  }

splitBy(cl, 5) foreach println

Output:

List(cs(abc1,3), cs(abc2,2))
List(cs(abc3,1), cs(abc4,2), cs(abc5,2))
List(cs(abc6,1), cs(abc7,5))

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