I'm new to Scala and learn it by solving some Project Euler problems. Here is the solution to problem 42 from Project Euler.

The task is to read words from file, then sum ordinal numbers of each character (e.g. sky =19+11+25=55). One need to count all words for which this sum is a triangular number (satisfying \$\frac{n(n+1)}{2}\$ for some \$n\$).

import scala.io.Source
import java.nio.file.Files
import java.nio.file.Paths

def makeTriangleNumber(word:String): Int = {
    word.map(ordinals(_)).reduceLeft(_ + _)

val inputFile = Paths.get(args(0));

val ordinals = ((('A' to 'Z').toList) zip Stream.from(1)).toMap
assume(ordinals('Z') == 26)

var triangleNumbers = scala.collection.SortedMap(1 -> 1)
val words = Source.fromFile(inputFile.toUri()).getLines.mkString.replace("\"", "").split(",").toList
var counter = 0

assert(makeTriangleNumber("SKY") == 55)

for (w <- words) {
    var candidate = makeTriangleNumber(w)
    if (!triangleNumbers.contains(candidate) && candidate > triangleNumbers.max._1) {

        var tmp = Stream.from(triangleNumbers.max._2 + 1)
          .map((x) => (x * (x + 1) / 2, x))
          .takeWhile(_._1 <= candidate)

        triangleNumbers = triangleNumbers ++ tmp.toMap
    if (triangleNumbers.contains(candidate)) {
        counter += 1
println("The answer is: " + counter)

1 Answer 1


The organization of the program is rather haphazard:

  1. It starts with a word value calculation function
  2. Then it checks for the existence of the input file
  3. Then defines the ordinals map (which the first function depends on!)
  4. Then it creates a triangleNumbers map
  5. Then it reads the words from the input file
  6. Then it counts the words that pass the triangularity test

Interspersed among that are three unit tests.

Surely these definitions could be grouped together more logically.

makeTriangleNumber is altogether wrongly named. It actually has nothing to do wth triangular numbers. Rather, it sums the values of the letters of a word. I would call it wordValue instead.

Within the makeTriangleNumber function, .reduceLeft(_ + _) could just be written as .sum.

Personally, I would simplify the definition of ordinals by taking advantage of Seq.zipWithIndex, then compensate for the off-by-one difference by adding the length of the word. (That may be a controversial move, since ordinals would no longer match the problem description.)

For extracting the words from the file, I suggest using Regex.findAllMatchIn(source:CharSequence), which gives you an Iterator of regular expression matches.

The heart of your program is the for loop. It's not very good functional programming, though. Ideally, the entire exercise should revolve around one line of code that looks like this:

words.count(word => isTriangularNumber(wordValue(word)))

How should isTriangularNumber be defined? In your loop, you're discovering triangular numbers like this:

var tmp = Stream.from(triangleNumbers.max._2 + 1)
  .map((x) => (x * (x + 1) / 2, x))
  .takeWhile(_._1 <= candidate)

… using the triangleNumbers map for memoization. I propose a more elegant definition — a lazy stream:

val triangularNumbers = Stream.from(1).scan(0)(_ + _)

Rather than the given formula, this definition relies on the recurrence principle that makes the numbers "triangular" in the first place.

Suggested solution

val wordValue = {
  val ordinals = ('A' to 'Z').zipWithIndex.toMap
  (word:String) => word.length + word.map(ordinals).sum

val isTriangularNumber = {
  val triangularNumbers = Stream.from(1).scan(0)(_ + _)
  (n:Int) => triangularNumbers.find(_ >= n).get == n

val inputFile = Source fromFile Paths.get(args(0)).toUri
val words = "\"([^\"]*)\"".r findAllMatchIn(inputFile.mkString) map(_.group(1))

val count = words.count(word => isTriangularNumber(wordValue(word)))
println("The answer is: " + count)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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