0

Given the following list :

val l = List("A", "A", "C", "C", "B", "C")

How can I add an auto-incrementing suffix to every elements so that I end up with a list containing no more duplicates, like the following (the ordering doesn't matter) :

List("A0", "A1", "C0", "C1", "C2", "B0")
cheseaux
  • 5,187
  • 31
  • 50

3 Answers3

4

I found it out by myself just after having written this question

val l = List("A", "A", "C", "C", "B", "C")
l.groupBy(identity) // Map(A->List(A,A),C->List(C,C,C),B->List(B))
  .values.flatMap(_.zipWithIndex) // List((A,0),(A,1),(C,0),(C,1),(C,2),(B,0))
  .map{ case (str, i) => s"$str$i"}

If there is a better solution (using foldLeft maybe) please let me know

cheseaux
  • 5,187
  • 31
  • 50
  • Important thing to note here is that you'll lose ordering because of the `groupBy`. – erip Jun 07 '17 at 12:43
  • 1
    Again, I don't care about the ordering. I could simply `sortBy` at the end, not a big deal – cheseaux Jun 07 '17 at 12:45
  • I think this might be as good as it gets without introducing mutable state. I was thinking about a map of counts and trying a `foldRight` and decrementing the counts as you encounter elements, but that's not so nice. – erip Jun 07 '17 at 12:55
  • I also started with `foldLeft` but got lost in the middle. – cheseaux Jun 07 '17 at 12:57
  • 2
    Nicely done. The `mapValues()` and `flatten` steps can be combined in a single `flatMap()` like so: `groupBy(identity).values.flatMap(_.zipWithIndex.map{case (s,i) => s"$s$i"})` – jwvh Jun 07 '17 at 16:45
1

In a single pass straightforward way :

def transformList(list : List[String]) : List[String] = {
  val buf: mutable.Map[String, Int] = mutable.Map.empty
  list.map {
    x => {
      val i = buf.getOrElseUpdate(x, 0)
      val result = s"${x.toString}$i"
      buf.put(x, i + 1)
      result
    }
  }
}

transformList( List("A", "A", "C", "C", "B", "C"))
C4stor
  • 8,355
  • 6
  • 29
  • 47
0

Perhaps not the most readable solution, but...

def appendCount(l: List[String]): List[String] = {
  // Since we're doing zero-based counting, we need to use `getOrElse(e, -1) + 1`
  // to indicate a first-time element count as 0. 
  val counts = 
    l.foldLeft(Map[String, Int]())((acc, e) => 
      acc + (e -> (acc.getOrElse(e, -1) + 1))
    )

  val (appendedList, _) = 
    l.foldRight(List[String](), counts){ case (e, (li, m)) =>
      // Prepend the element with its count to the accumulated list.
      // Decrement that element's count within the map of element counts
      (s"$e${m(e)}" :: li, m + (e -> (m(e) - 1)))
    }
  appendedList
}

The idea here is that you create a count of each element in the list. You then iterate from the back of the list of original values and append the count to the value while decrementing the count map.

You need to define a helper here because foldRight will require both the new List[String] and the counts as an accumulator (and, as such, will return both). You'll just ignore the counts at the end (they'll all be -1 anyway).

I'd say your way is probably more clear. You'll need to benchmark to see which is faster if that's a concern.

Ideone.

erip
  • 16,374
  • 11
  • 66
  • 121
  • 1
    I think using `groupBy(identity).mapValues(_.length)` would be better compared to the complexity of using Map accesses and dealing with `getOrElse` – cheseaux Jun 07 '17 at 13:28
  • The `getOrElse` calls in the `foldRight` are unnecessary. Edited. I think yours is more readable, but I think mine is faster. – erip Jun 07 '17 at 13:29