2
votes

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

3

3 Answers

2
votes

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))
2
votes

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) }
}
1
votes

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
}