Metadata
-
Date
-
Tagged
-
Part of series
-
Older post
-
Newer post
Advent of Code 2015 Day 4
Day 4: The Ideal Stocking Stuffer
https://adventofcode.com/2015/day/4
Santa needs help mining AdventCoin to use as stocking stuffers.
To do this, he needs to find MD5 hashes that start with at least 5 zeros in hexadecimal.
Today’s input is a secret key.
An example input looks like this:
2 parts form the input to the MD5 hash:
- The secret key
- A number (an integer without leading zeros)
- If your secret key is abcdef, the lowest number that produces a hash that starts with 5 zeros is 609043. The MD5 hash of abcdef609043 start with 000001dbbfa…
- If your secret key is pqrstuv, the lowest number that produces a hash that starts with 5 zeros is 1048970. The MD5 hash of pqrstuv1048970 starts with 000006136ef…
Part 1
The question asks for the lowest number that produces a hexadecimal MD5 hash that starts with 5 zeros.
Option 1: A for loop
Some skeleton/pseudo-code to start with:
A bruteforce algorithm that calculates the hash 1 at a time and checks if it contains 5 zeros.
I’m not going to write the MD5 algorithm myself.
Time for code someone else wrote, the md-5
crate
An MD5 hash is 16 bytes.
A byte is often represented as a pair of hexadecimal characters.
Singular hex characters go from 0
to F
, so those pairs go from 00
to FF
.
In decimal that would be from 0
to 255
.
The result of the MD5
implementation I used returns those 16 bytes in a list.
There are 3 steps to get that list:
- Create a
hasher
- Put bytes into it
- Tell it to create a hash with that input
That’s the part that creates a hash dealt with. Now the part that checks that hash (does it start with 5 zeros?).
The first byte contains information for the first 2 hex characters. The second byte contains information for the next 2 hex characters.
If a byte is zero, both hex characters are zero.
We check if first four hex characters are 0 by checking if their entire byte is 0.
hash[0] == 0 && hash[1] == 0
The 5th hex character is the first character of a pair.
With some bit logic we turn the second character into a 0.
Then we check if the resulting number is 0:
hash[2] & 0xF0 == 0
That turns the entire check into:
This can be written a bit differently. We bitwise OR every part first If the result is 0, they were all 0.
Putting it all together.
I pulled the hasher
outside of the for
loop to reuse it.
Creating a new one every iteration works too, reusing it avoids a bit of unnecessary work.
This means the call to finalize()
turns into a call to finalize_reset()
, clearing the hasher
so it’s ready for the next loop.
Option 2: An iterator chain
This uses an open range again to loop from 0 until the maximum possible number.
The used number is indentical to the index in that sequence. When a hash with 5 zeros is found, we break the loop and return that index.
Option 3: Parallellize it
This problem requires a bunch of work that can be split up, a great scenario for multi threading.
I decided to slightly tweak the solution with the iterator chain.
The rayon
crate provides the tools to convert that code with minimal changes.
The open range explicitly has an end point now. This lets us convert the iterator chain to a parallel iterator.
The call to position
turned into a call to rayon
’s position_first
.
Notice the hasher
is inside the loop again.
Curiously, on my machine (an older intel quadcore without hyperthreading), the gains from multithreading do not outweight the added overhead. The algorithm was marginally slower multithreaded than the same one ran sequentially.
Bonus: Avoiding string allocations
In every loop, we turn the number into a String
only to immediately feed it to the hasher
.
Avoiding that String
creation can speed up the solution.
Using a crate like numtoa
can help with this.
The for
loop code then turns into:
Main code for part 1
Part 2
The question asks for the lowest number that produces a hexadecimal MD5 hash that starts with 6 zeros.
The code is almost identical to part 1.
That special logic to deal with the 5’th hex character can be deleted now.
The part where we check if a hash starts with zeroes changes to:
Ironically, this makes part2 simpler to code than part1.