1
\$\begingroup\$

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 -> 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)
200_success
146k22 gold badges190 silver badges479 bronze badges
asked Sep 16, 2016 at 13:22
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

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)
answered Sep 17, 2016 at 9:42
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.