Lundahl
Lundahl

Reputation: 6572

Conditionally merge list elements

I want to conditionally merge elements following eachother in a list based on some expression. An example to better explain what I would like to do:

from:

val list = List("a1", "a2", "b1", "b2", "b3", "a3", "a4")

I would like to merge all elements starting with b into a single element to get a resulting list like this:

List("a1", "a2", "b1-b2-b3", "a3", "a4")

In my use case, the b-elements always follow in sequence but the number of b-elements can vary from no elements to tens of elements.

I've tried doing something like

list.foldLeft("")((s1, s2) => if (s1.matches("""b\d""") && s2.matches("""b\d""")) s1 + "-" + s2 else s1)

but it doesn't render me anything useful.

Any suggestions on how to approach this?

Upvotes: 2

Views: 848

Answers (2)

Marius Danila
Marius Danila

Reputation: 10431

This should work

def aggregateByPrefix(xs: Seq[String], prefix: String) = {
  val (xs1, xs2) = xs.span(x => !x.startsWith(prefix))
  val (withPrefix, withoutPrefix) = xs2.partition(_.startsWith(prefix))
  val aggregated = withPrefix.mkString("-")
  xs1 ++ Seq(aggregated) ++ withoutPrefix
}

Usage:

aggregateByPrefix(List("a1", "a2", "b1", "b2", "b3", "a3", "a4"), "b")
=> List(a1, a2, b1-b2-b3, a3, a4)

Upvotes: 0

huynhjl
huynhjl

Reputation: 41646

It can be done with a foldLeft and looking at the most recently inserted element of the list:

list.foldLeft(List[String]()) {
  case (Nil, str) => str :: Nil
  case (head :: tail, str) =>
    if (head(0) == 'b' && str(0) == 'b') (s"$head-$str") :: tail
    else str :: head :: tail
}.reverse 
//> res0: List[String] = List(a1, a2, b1-b2-b3, a3, a4)

The pattern match can also be rewritten (if you find it clearer):

list.foldLeft(List[String]()) {
  case (head :: tail, str) if (head(0) == 'b' && str(0) == 'b') =>
    (s"$head-$str") :: tail
  case (other, str) =>
    str :: other
}.reverse    

Upvotes: 3

Related Questions