# Tail-Recursion to get a Map of word counts

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] = {
else {
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?

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.

• I didn't consider that, thank you for the tip/review. – Cam Hashemi Apr 30 '15 at 3:41

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


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.