Reputation: 1150
I'm new at scala and trying to write function which returns map with all indices for each letter at given string. My code:
def group(string: String) = {
val map = mutable.Map[Char, ListBuffer[Int]]()
for (i <- string.indices) {
val ch = string(i)
if(map.contains(ch)) map(ch) += i
else map += (ch -> ListBuffer(i))
}
map
}
When I'm trying to compile I have an error:
Error:(14, 30) type arguments [?,Iterable[Any] with PartialFunction[Int with Char,Any] with scala.collection.generic.Subtractable[_ >: Int with Char <: AnyVal, Iterable[Any] with PartialFunction[Int with Char,Any] with scala.collection.generic.Subtractable[_ >: Int with Char <: AnyVal, Iterable[Any] with PartialFunction[Int with Char,Any] with scala.collection.generic.Subtractable[_ >: Int with Char <: AnyVal, Equals]]{def seq: Iterable[Any] with PartialFunction[Int with Char,Any]}]{def seq: Iterable[Any] with PartialFunction[Int with Char,Any]{def seq: Iterable[Any] with PartialFunction[Int with Char,Any]}}] do not conform to trait Subtractable's type parameter bounds [A,+Repr <: scala.collection.generic.Subtractable[A,Repr]] val v = for (i <- string.indices) {
It seems that something wrong with value of the loop. So I've added to the last line of loop 'true' and now everything works fine:
def group(string: String) = {
val map = mutable.Map[Char, ListBuffer[Int]]()
for (i <- string.indices) {
val ch = string(i)
if(map.contains(ch)) map(ch) += i
else map += (ch -> ListBuffer(i))
true
}
map
}
What's wrong in my code, and how can I fix it? Scala version: 2.12.6
Upvotes: 2
Views: 1998
Reputation: 1159
You don't need to keep a ListBuffer for keeping the indexes, we can just update the map entry with a new list of appended index.
def group(string: String): mutable.Map[Char, List[Int]] = {
val map = mutable.Map.empty[Char, List[Int]]
string.zipWithIndex.foreach { case (char: Char, index: Int) =>
map += (char -> (index :: map.get(char).getOrElse(List())))
}
map
}
Upvotes: 1
Reputation: 1240
As you can see in https://docs.scala-lang.org/tutorials/FAQ/yield.html
Scala’s “for comprehensions” are syntactic sugar for composition of multiple operations with foreach, map, flatMap, filter or withFilter. Scala actually translates a for-expression into calls to those methods, so any class providing them, or a subset of them, can be used with for comprehensions.
So, your for
loop
for (i <- string.indices) {
val ch = string(i)
if(map.contains(ch)) map(ch) += i
else map += (ch -> ListBuffer(i))
}
equivalent to
string.indices.foreach(i => {
val ch = string(i)
if(map.contains(ch)) map(ch) += i
else map += (ch -> ListBuffer(i))
})
foreach
method interface is
def foreach[U](f: A => U): Unit
map(ch) += i
returns ListBuffer
, but map += (ch -> ListBuffer(i))
returns Map
. And when the compiler tries to identify U
in foreach
argument f: Int => U
, it gets something between ListBuffer
and Map
and doesn`t compile it.
Also, the compiler doesn't check result type of if-else expression if you don't use it somewhere.
You can fix your code by rewriting it like this
def group(string: String) = {
val map = mutable.Map[Char, ListBuffer[Int]]()
def update(i: Int): Unit = {
val ch = string(i)
if(map.contains(ch)) map(ch) += i
else map += (ch -> ListBuffer(i))
}
for (i <- string.indices) update(i)
map
}
But better use standard methods
def group(string: String) = string.toCharArray.zipWithIndex.groupBy(_._1).mapValues(_.map(_._2))
Upvotes: 2
Reputation: 149598
The reason your method isn't compiling is because you're returning completely different types from your if-else
expression. One is returning ListBuffer[Int]
, and the other returns a Map[Char, ListBuffer[Int]]
.
What you want is:
def group(string: String): mutable.Map[Char, ListBuffer[Int]] = {
val map = mutable.Map[Char, ListBuffer[Int]]()
for (i <- string.indices) {
val ch = string(i)
if (map.contains(ch)) {
map(ch) += i
map
} else map += (ch -> ListBuffer(i))
}
map
}
An additional approach without a mutable Map
or ListBuffer
could be:
def group(s: String): Map[Char, Int] = {
s.split("\\W+")
.flatten
.zipWithIndex
.groupBy { case (char, _) => char }
.mapValues { arr => arr.map(_._2) }
}
Upvotes: 2