4

I have a List[Int], perhaps like this:

List(1,2,3,3,4,5,6,6,7,8,9)

There will be occasional duplication (always 2, not more). When there's a dup I want to merge the elements together with a function. So in this simple example if my function is to add the 2 numbers together I'd get:

List(1,2,6,4,5,12,7,8,9)

What's a concise way to do this? List.map() only looks at/transforms 1 element at a time.

Greg
  • 10,696
  • 22
  • 68
  • 98

2 Answers2

2

You can use .foldLeft with new list as accumulator (assuming the list has duplicates next to each other)

def mergeDuplicates(list: List[Int]): List[Int] = {
  list.foldLeft(List.empty[Int]) {
    case (l, elem) if l.lastOption.contains(elem) =>
      l.dropRight(1) :+ (2 * elem)
    case (l, elem) =>
      l :+ elem
  }
}

println(mergeDuplicates(List(1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 9))) 

// output List(1, 2, 6, 4, 5, 12, 7, 8, 9)

using extension method,

implicit class ListOps(list: List[Int]) {
  def mergeDuplicates: List[Int] = {
    list.foldLeft(List.empty[Int]) {
      case (l, elem) if l.lastOption.contains(elem) =>
        l.dropRight(1) :+ (2 * elem)
      case (l, elem) =>
        l :+ elem
    }
  }
}

val mergedList = List(1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 9).mergeDuplicates
println(mergedList)
prayagupa
  • 30,204
  • 14
  • 155
  • 192
  • 3
    I think it makes more sense to `foldRight` and pre-pend `::` rather than `foldLeft` and append `:+`. Especially when building a `List`. – jwvh Jun 21 '19 at 05:53
  • Especially since `lastOption` is fairly O(n) on list, it would be much better to prepend and `headOption` and reverse at the end – Levi Ramsey Jun 21 '19 at 15:13
2

Starting Scala 2.13, you could use the List#unfold combined with List#span:

// val items = List(1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 9)
List.unfold(items) {
  case Nil  => None
  case rest => Some(rest.span(_ == rest.head))
}
.map(_.sum)
// List(1, 2, 6, 4, 5, 12, 7, 8, 9)

or even, coupled with Scala 2.13's Option#unless builder:

List
  .unfold(items)(rest => Option.unless(rest.isEmpty)(rest.span(_ == rest.head)))
  .map(_.sum)

  • Unfold uses an internal state which is initialized in our case with the list List(1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 9) for which to sum consecutive duplicates.
  • During each iteration, we span that internal state in order to find the heading consecutive items: rest.span(_ == rest.head) (which for instance during the third iteration on the List(3, 3, 4, 5, ...) internal state gives (List(3, 3), List(5, 6, ...))).
  • Since unfold expects within each iteration an Option of a tuple whose first part is the new element to add to the list being built (during the first iteration for instance List(1)) and whose second part is the new value of the internal state (List(2, 3, 3, ...)), that span fits exactly that requirement.
  • We keep iterating the same step until the internal list is empty, in which case returning None signals the unfold that we're finished and that the iteration has to stop.
  • Finally, we replace grouped parts (List(List(1), ..., List(3, 3), ...)) by their sum with a simple .map(_.sum). This can of course also be achieved within the unfolding at the cost of some additional boilerplate.
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190