Triangle numbers finder in Scala 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));
assert(Files.exists(inputFile))

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

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

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

for (w &lt;- words) {
    var candidate = makeTriangleNumber(w)
    if (!triangleNumbers.contains(candidate) &amp;&amp; candidate &gt; triangleNumbers.max._1) {

        var tmp = Stream.from(triangleNumbers.max._2 + 1)
          .map((x) =&gt; (x * (x + 1) / 2, x))
          .takeWhile(_._1 &lt;= candidate)

        triangleNumbers = triangleNumbers ++ tmp.toMap
    }
    if (triangleNumbers.contains(candidate)) {
        counter += 1
    }
}
println("The answer is: " + counter)
The organization of the program is rather haphazard:


It starts with a word value calculation function
Then it checks for the existence of the input file
Then defines the ordinals map (which the first function depends on!)
Then it creates a triangleNumbers map
Then it reads the words from the input file
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 =&gt; 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) =&gt; (x * (x + 1) / 2, x))
  .takeWhile(_._1 &lt;= 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) =&gt; word.length + word.map(ordinals).sum
}

val isTriangularNumber = {
  val triangularNumbers = Stream.from(1).scan(0)(_ + _)
  (n:Int) =&gt; triangularNumbers.find(_ &gt;= 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 =&gt; isTriangularNumber(wordValue(word)))
println("The answer is: " + count)