Andrei Koch
Andrei Koch

Reputation: 1150

Scala: type arguments do not conform to trait Subtractable's type parameter bounds

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

Answers (3)

lprakashv
lprakashv

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

Aleksey Isachenkov
Aleksey Isachenkov

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

Yuval Itzchakov
Yuval Itzchakov

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

Related Questions