Advent of Code 2016 – Day 5( Java 8)

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.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s