sam
sam

Reputation: 71

Scala: Add a sequence number to duplicate elements in a list

I have a list and want to add a sequential number to duplicate elements.

val lst=List("a", "b", "c", "b", "c", "d", "b","a") 

The result should be

List("a___0", "b___0", "c____0", "b___1", "c____1", "d___0", "b___2","a___1")

preserving the original order.

What I have so far:

val lb=new ListBuffer[String]()
for(i<-0 to lst.length-2) {
  val lbSplit=lb.map(a=>a.split("____")(0)).distinct.toList
  if(!lbSplit.contains(lst(i))){
    var count=0
    lb+=lst(i)+"____"+count

    for(j<-i+1 to lst.length-1){
      if(lst(i).equalsIgnoreCase(lst(j))) {
        count+=1
        lb+= lst(i)+"____"+count
      }
    }
  }
}

which results in :

res120: scala.collection.mutable.ListBuffer[String]
  = ListBuffer(a____0, a____1, b____0, b____1, b____2, c____0, c____1, d____0)

messing up the order. Also if there is a more concise way that would be great.

Upvotes: 1

Views: 550

Answers (2)

jwvh
jwvh

Reputation: 51271

This should work without any mutable variables.

val lst=List("a", "b", "c", "b", "c", "d", "b","a")

lst.foldLeft((Map[String,Int]().withDefaultValue(0),List[String]())){
  case ((m, l), x) => (m + (x->(m(x)+1)), x + "__" + m(x) :: l)
}._2.reverse
// res0: List[String] = List(a__0, b__0, c__0, b__1, c__1, d__0, b__2, a__1)

explanation

  • lst.foldLeft - Take the List of items (in this case a List[String]) and fold them (starting on the left) into a single item.
  • (Map[String,Int]().withDefaultValue(0),List[String]()) - In this case the new item will be a tuple of type (Map[String,Int], List[String]). We'll start the tuple with an empty Map and an empty List.
  • case ((m, l), x) => - Every time an element from lst is passed in to the tuple calculation we'll call that element x. We'll also receive the tuple from the previous calculation. We'll call the Map part m and we'll call the List part l.
  • m + (x->(m(x)+1)) - The Map part of the new tuple is created by creating/updating the count for this String (the x) and adding it to the received Map.
  • x + "__" + m(x) :: l - The List part of the new tuple is created by pre-pending a new String at the head.
  • }._2.reverse - The fold is finished. Extract the List from the tuple (the 2nd element) and reverse it to restore the original order of elements.

Upvotes: 2

Matt Fowler
Matt Fowler

Reputation: 2733

I think a more concise way that preserves the order would just to be to use a Map[String, Int] to keep a running total of each time you've seen a particular string. Then you can just map over lst directly and keep updating the map each time you've seen a string:

var map = Map[String, Int]()
lst.map { str =>
  val count = map.getOrElse(str, 0) //get current count if in the map, otherwise zero
  map += (str -> (count + 1)) //update the count
  str + "__" + count
}

which will give you the following for your example:

List(a__0, b__0, c__0, b__1, c__1, d__0, b__2, a__1)

I consider that easiest to read, but if you want to avoid var then you can use foldLeft with a tuple to hold the intermediate state of the map:

lst.foldLeft((List[String](), Map[String, Int]())) { case ((list, map), str) =>
  val count = map.getOrElse(str, 0)
  (list :+ (str + "__" + count), map + (str -> (count + 1)))
}._1

Upvotes: 1

Related Questions