This one liner...
Console.println(io.Source.fromFile("names.txt").getLines.mkString.split(",").map{x:String => x.slice(1, x.length -1)}.sortBy { x => x}.zipWithIndex.map{t =>{ (t._2 +1)*(t._1.map{_.toChar - "A"(0).toChar + 1}.sum)}}.sum);
... is my solution to Project Euler problem 22. It seems to work, and it's written in (my attempt at) functional style.
This example is a bit extreme, but my question is a bit more general - how do you prefer to write/format/comment functional style code? The functional approach seems to encourage a sequence of method calls, which I find can get unreadable, and also leaves nowhere obvious to put comments.
Also, when I write procedural code, I find I write small methods each with one purpose and with meaningful names. When I write functional code, I seem to be developing a habit that produces lines a little like the one above, where (to me) the meaning is difficult to decipher - and also the individual computations are difficult to re-use elsewhere. Quite a lot of the functional code examples I see on the web are similarly terse (to me) obscure.
What should I be doing? Writing little functions for each part of the computatation with names meaningful in the current context? (even if they're little more than a wrapper for map, say?)
For the example I've given, what are better ways of writing it, and presenting it?
(Like all style questions, this one is subjective. There's no reason it should get argumentative, though!)
A trivial first-attempt at tidying it up is to just remove the leading Console.
the trailing ;
and the explicit :String
type - all of which are unnecessary - add some indentation and import io.Source:
import io.Source
println(
Source.fromFile("names.txt").getLines.mkString.split(",").map{
x => x.slice(1, x.length -1)
}.sortBy {x => x}.zipWithIndex.map{
t =>{ (t._2 +1)*(t._1.map{_.toChar - "A"(0).toChar + 1}.sum)}
}.sum
)
The next step is to clean it up a little, use pattern matching when mapping over list of tuples and identity
instead of x=>x
. toChar
is also unnecessary for characters, and single quotes can be used to represent character literals.
import io.Source
println(
Source.fromFile("names.txt").getLines.mkString.split(",").map {
x => x.slice(1, x.length -1)
}.sortBy(identity).zipWithIndex.map {
case (v, idx) =>{ (idx+1)*(v.map{_ - 'A' + 1}.sum)}
}.sum
)
A few more changes also help make the intent of the code far clearer:
import io.Source
println(
Source.fromFile("names.txt").getLines.mkString.split(",")
.map { _.stripPrefix("\"").stripSuffix("\"") }
.sortBy(identity)
.map { _.map{_ - 'A' + 1}.sum }
.zipWithIndex
.map { case (v, idx) => (idx+1) * v }
.sum
)
The next step, to make it more "functional", is to break it into "functions" (sneaky, huh?). Ideally each function will have a name clearly expressing its purpose, and it will be short (aim for it to be a single expression, so braces aren't required):
import io.Source
def unquote(s:String) = s.stripPrefix("\"").stripSuffix("\"")
def wordsFrom(fname:String) =
Source.fromFile(fname).getLines.mkString.split(",").map(unquote)
def letterPos(c:Char) = c - 'A' + 1
println(
wordsFrom("names.txt")
.sortBy(identity)
.map { _.map(letterPos).sum }
.zipWithIndex
.map { case (v, idx) => (idx+1) * v }
.sum
)
wordsFrom
is an obvious 1-liner, but I split it for easier formatting on stackOverflow
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With