2
\$\begingroup\$

I want to read a file and store the number of occurrences of each word in a Map, using tail recursion. I came up with the following; it seems to work; does it look like it's right?

def countWordsinFile(reader:java.util.Scanner, wordCounts:Map[String,Int]): Map[String,Int] = {
    if (!reader.hasNext()) wordCounts
    else {
        val word = reader.next()
        countWordsinFile(reader, wordCounts + (word -> {wordCounts.getOrElse(word,0)+1}))
    }
}
// the function is called with a reader from a .txt file and wordCounts is an empty Map()

Is this tail-recursive? If not, then how can I make it so?

\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

Yes, this is tail-recursive. Just annotate countWordsInFile with @tailrec and the compiler will check that for you. Btw: why not name the method countWords? The Scanner is not limited to read from files.

\$\endgroup\$
1
  • \$\begingroup\$ I didn't consider that, thank you for the tip/review. \$\endgroup\$ Commented Apr 30, 2015 at 3:41
2
\$\begingroup\$

it seems to work; does it look like it's right?

It works, and it looks "OK", but it could be better:

  • When a method call doesn't mutate the state, it's customary to omit the parentheses. I'm talking about the reader.hasNext(), the recommended way is to write as reader.hasNext

  • Users of this function should not have to know to pass in an empty Map. It would be more ergonomic to declare the map parameter with a default value of Map.empty

  • Instead of using the + operator to append to the map, a slightly more compact and more idiomatic way is to use the map.updated(key, value) method

  • Since the "reader" is a Scanner, I'd call it "scanner" instead

Something like this:

object WordCounts extends App {

  println(countWordsinFile(new Scanner(new File("/tmp/words"))))

  @tailrec
  def countWordsinFile(scanner: Scanner, wordCounts: Map[String, Int] = Map.empty): Map[String, Int] = {
    if (!scanner.hasNext) wordCounts
    else {
      val word = scanner.next()
      countWordsinFile(scanner, wordCounts.updated(word, wordCounts.getOrElse(word, 0) + 1))
    }
  }
}
\$\endgroup\$
0
\$\begingroup\$

Here is another way to count the number of words occurrences.

def numberOfWordOccurrences(words: Traversable[String]): Map[String, Int] = words.foldLeft(Map[String, Int]()) {
    case (occurrences, word) if (occurrences.get(word) == None) => occurrences + (word -> 1)
    case (occurrences, word) => occurrences + (word -> (occurrences(word) + 1))
}

Notes:

It is not tail-recursive. It is based on the foldLeft function of scala collections. I am not familiar with java.util.Scanner so you would have to look how you can get the input as a scala collection type. Also there are two scala types:

  • scala.util.parsing.combinator.lexical.Scanners and
  • scala.util.parsing.combinator.lexical.Scanner

I am not familiar with them, too, but maybe they do something similar like the Java class in a more Scala-ish way.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.