1
\$\begingroup\$

This function finds the 8 char passcode from a given "doorId".

Is there a way to find the passcode in an undetermined number of steps without using a while? Maybe involving streams? val passcode = stream(logic).take(8).

def findPassCode(doorId: String): String = {
  var passcode = ""
  var count = 0

  while (passcode.length < 8) {
    val hash = md5(doorId + count)
    if (hash.startsWith("00000")) passcode = passcode + hash.drop(5).take(1)
    count = count + 1
  }

  passcode
}
\$\endgroup\$
  • 4
    \$\begingroup\$ This code appears to be for 2016 Advent of Code, Day 5. It would be nice if you would include such contextual information in the question. \$\endgroup\$ – 200_success Dec 6 '16 at 5:29
1
\$\begingroup\$

First of all, you have the passcode length hard-coded. It would be better to make it a parameter, perhaps with a default value. The same goes for the string the "interesting" hashes start with ("00000").

Also, there are several ways you could make the code more functional. Here are a couple ideas:

  • Use recursion: Computing an \$n\$ character pascode is accomplished by first computing an \$n-1\$ character passcode, then computing the next character. Or, you could rewrite the loop in a tail-recursive form.
  • You could use streams. Streams are Scala's version of a lazy list / sequence.

Using streams, we can use the following logic:

  1. Generate a stream of consecutive Ints, starting at 0.
  2. Combine each number in with doorId
  3. Compute the hash of each combined string
  4. Filter the hashes, keeping only hashes that start with five '0's
  5. Take the sixth character of each hash
  6. Take the first 8 - doorId.length characters, and append them to doorId

We can express this in code using something like:

def findPassCode(doorId: String): String = {
  val chars = Stream.from(0)                            // Step 1
                    .map(n => doorId + n)               // Step 2
                    .map(md5)                           // Step 3
                    .filter(h => h.startsWith("00000")) // Step 4
                    .map(h => h(5))                     // Step 5
  doorId + chars.take(8 - doorId.length).mkString       // Step 6
}
\$\endgroup\$
  • \$\begingroup\$ Thank you, really helpful! I always want to take 8 characters from the stream. Thats the nature of the passcode. So what I want is simply doorId + chars.take(8).mkString. Your code worked without other modifications! Really happy to get rid of the loop and conditionals. They are festering grounds of bugs. \$\endgroup\$ – Red Mercury Dec 7 '16 at 14:42
  • \$\begingroup\$ @RedMercury, ah yes, I think I misunderstood exactly what the passcode was supposed to be slightly. Glad you found it useful, though. You're right about looping (and explicit recursion) -- it should be avoided if possible. I agree that hard-coding for 8 characters is ok for a simple exercise. But remember people tend to do as they practice -- so it's better to always practice best practices (at least to a point). \$\endgroup\$ – Nathan Davis Dec 7 '16 at 17:14
  • \$\begingroup\$ The implementation could be simplified further as Stream.from(0).map(n => md5(doorId + n)).filter(_.startsWith("00000")).map(_(5)).take(8).mkString. \$\endgroup\$ – 200_success Dec 8 '16 at 7:17
  • \$\begingroup\$ I found that the functional solution using a Stream, though more elegant, performed much poorer than the original code. \$\endgroup\$ – 200_success Dec 8 '16 at 7:18

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.