Reputation: 69
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))
Upvotes: 0
Views: 1817
Reputation: 22497
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))
Upvotes: 0
Reputation: 1868
@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)))
Upvotes: 1