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\$
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\$
  • \$\begingroup\$ I didn't consider that, thank you for the tip/review. \$\endgroup\$ – Cam Hashemi Apr 30 '15 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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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