Previous:
Advent of Code 2016 – Day 4 (Perl 6)
Advent of Code 2016 – Day 3 (Clojure)
Advent of Code – Day 2 (Python 3)
Advent Of Code 2016 – Day 1(Haskell)
Day 5
Start: 12/7/2016
Finish 12/7/2016
Language: Java 8
SPOILER ALERT: If you have any inkling, any whatsoever, to work on the Advent of Code…. DO NOT READ THIS BLOG POST. DO NOT LOOK AT MY GITHUB PROJECT. It is no fun if you’re not solving it yourself, and you’ll feel so much better about yourself if you can figure it out without looking up an answer. This blog post is not to give away the answer, but instead, is there for people to learn from.
As always, the following code is in my GitHub: https://github.com/pviafore/AdventOfCode2016
The Challenge
So I really haven’t touched Java since my first job. So, I saw a challenge that involved MD5, thought to the Java library that I remember using way back when, and decided to polish up some skills. I wanted to dig into Java 8 since the introduction of lambda expressions and streams, but haven’t had a reason to.
So this time around, I had to MD5 a combination of a key and ever-incrementing numbers until I found 8 hashes that had a special property (started with 5 zeroes when converted to a string). Then we had to grab the sixth character of those hashes and that forms our password. So this sounded perfect for an infinite stream. Let’s take a look at the full code before I dive in. (It’s a bit more verbose than others, but hey, it’s Java.)
import java.security.MessageDigest;
import java.util.stream.IntStream;
class Challenge1
{
static String doorId = "ffykfhsq";
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes)
{
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j >> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static String md5(int index)
{
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
String str = doorId+new Integer(index).toString();
byte[] digest = md.digest(str.getBytes("UTF-8"));
return new String(Challenge1.bytesToHex(digest));
}
catch(Exception e)
{
return "";
}
}
public static boolean isAGoodHash(int index)
{
String hash = Challenge1.md5(index);
return hash.startsWith("00000");
}
public static int getKey(int index)
{
String hash = Challenge1.md5(index);
return hash.charAt(5);
}
public static void main(String[] args)
{
IntStream.iterate(0, i-> i+1)
.filter(Challenge1::isAGoodHash)
.limit(8)
.map(Challenge1::getKey)
.forEach(i -> System.out.print((char)i));
System.out.println();
}
}
The main part is pretty self explanatory. Start with an infinite sequence 1,2,3……., filter out anything that isn’t a good hash, take the first 8 and grab the keys, finally printing them all out.
public static boolean isAGoodHash(int index)
{
String hash = Challenge1.md5(index);
return hash.startsWith("00000");
}
public static int getKey(int index)
{
String hash = Challenge1.md5(index);
return hash.charAt(5);
}
public static void main(String[] args)
{
IntStream.iterate(0, i-> i+1)
.filter(Challenge1::isAGoodHash)
.limit(8)
.map(Challenge1::getKey)
.forEach(i -> System.out.print((char)i));
System.out.println();
}
The MD5 code was able to heavily borrow off of java.security.MessageDigest to create the MD5
public static String md5(int index)
{
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
String str = doorId+new Integer(index).toString();
byte[] digest = md.digest(str.getBytes("UTF-8"));
return new String(Challenge1.bytesToHex(digest));
}
catch(Exception e)
{
return "";
}
}
Finally, I took some code off the internet to convert a byte string to hex (I’d written this so, so many times before in Java that I didn’t feel like writing it once again.
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes)
{
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j >> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
And that was it! That was all the code it took. I really like how expressive streams are in languages that support that type of composability. It reminds me of Clojure’s pipeline in Day 3, and I’m sure we’ll see it again in Elixir and Elm.
Part 2
Here was a kicker for the FP enthusiast in me. It wasn’t enough to just grab the first 8 hashes anymore. The fifth character in each hash was now the position of the final character in the password, and the sixth character was the character itself. Any hash that didn’t have a position 0-7 should have been ignored, and only the first time the position is seen should we update the key.
I updated the isAGoodHash to at least check position 0-7
public static boolean isAGoodHash(String hash)
{
return hash.startsWith("00000") && isValidPosition(hash.charAt(5));
}
This means I have to track state through iteration. I took a very precursory glance through Java docs to try and figure out if there was a way to use reduce or collect to stop iteration when I the password was filled out, but wasn’t finding anything. (I’m sure there’s something, I just didn’t have anything at the time.)
So, rather than try to make pure FP and bend the rules, I put in an explicit for loop to indicate that I’m mutating state.
public static void main(String[] args)
{
Iterator it = IntStream.iterate(0, i-> i+1)
.mapToObj(Challenge2::md5)
.filter(Challenge2::isAGoodHash)
.iterator();
List keys = Arrays.asList('-', '-','-','-','-','-','-','-');
while (it.hasNext())
{
String s = it.next();
char pos = (char)(s.charAt(5) - 48);
char c = s.charAt(6);
if(keys.get(pos) == '-')
{
keys.set(pos, c);
}
if(!keys.contains('-'))
{
break;
}
}
keys.stream().forEach(c -> System.out.print(c));
System.out.println();
}
I found mapToObj this time around, which let me convert them to a String right away rather than dealing with ints the whole way through.
Wrap-up
I liked using Streams and Lambda Expressions in Java 8. It definitely cuts down on some of the verbosity that plagued Java programs of the past. However, it still had it’s limits, and Java as a whole felt fairly verbose to me.
I give this exercise of mine an A-. I was able to use what I wanted, learned some new things, and got the problems right on my first try. Next up, I’m going to take a stab at a numerical computing language, Julia.
16 thoughts on “Advent of Code 2016 – Day 5( Java 8)”