Reputation: 71
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
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
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