day 10 solution 1

This commit is contained in:
Yann Esposito (Yogsototh) 2017-12-10 13:07:53 +01:00
parent b613bf7e45
commit ebcb071e98
Signed by untrusted user who does not match committer: yogsototh
GPG key ID: 7B19A4C650D59646
6 changed files with 201 additions and 1 deletions

View file

@ -37,6 +37,7 @@ library
Day7
Day8
Day9
Day10
other-modules:
Paths_adventofcode
build-depends:

View file

@ -15,6 +15,7 @@ import qualified Day6
import qualified Day7
import qualified Day8
import qualified Day9
import qualified Day10
showSol :: [Char] -> Doc -> IO ()
showSol txt d = putText . toS . render $
@ -35,6 +36,7 @@ solutions = Map.fromList [(["1"], day1)
,(["7"], day7)
,(["8"], day8)
,(["9"], day9)
,(["10"], day10)
]
day1 :: IO ()
@ -95,3 +97,9 @@ day9 = do
showSol "Solution 1" (int sol1)
sol2 <- Day9.solution2
showSol "Solution 2" (int sol2)
day10 :: IO ()
day10 = do
putText "Day 10:"
let sol1 = Day10.solution1 Day10.input
showSol "Solution 1" (int sol1)

View file

@ -22,6 +22,7 @@ library:
- Day7
- Day8
- Day9
- Day10
dependencies:
- base >=4.7 && <5
- protolude

185
src/Day10.hs Normal file
View file

@ -0,0 +1,185 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-|
description:
You come across some programs that are trying to implement a software emulation
of a hash based on knot-tying. The hash these programs are implementing isn't
very strong, but you decide to help them anyway. You make a mental note to
remind the Elves later not to invent their own cryptographic functions.
This hash function simulates tying a knot in a circle of string with 256 marks
on it. Based on the input to be hashed, the function repeatedly selects a span
of string, brings the ends together, and gives the span a half-twist to reverse
the order of the marks within it. After doing this many times, the order of the
marks is used to build the resulting hash.
4--5 pinch 4 5 4 1
/ \ 5,0,1 / \/ \ twist / \ / \
3 0 --> 3 0 --> 3 X 0
\ / \ /\ / \ / \ /
2--1 2 1 2 5
To achieve this, begin with a list of numbers from 0 to 255, a current position
which begins at 0 (the first element in the list), a skip size (which starts at
0), and a sequence of lengths (your puzzle input). Then, for each length:
- Reverse the order of that length of elements in the list,
starting with the element at the current position.
- Move the current position forward by that length plus the skip size.
- Increase the skip size by one.
The list is circular; if the current position and the length try to reverse
elements beyond the end of the list, the operation reverses using as many extra
elements as it needs from the front of the list. If the current position moves
past the end of the list, it wraps around to the front. Lengths larger than the
size of the list are invalid.
Here's an example using a smaller list:
Suppose we instead only had a circular list containing five elements:
0, 1, 2, 3, 4, and were given input lengths of 3, 4, 1, 5.
- The list begins as [0] 1 2 3 4
(where square brackets indicate the current position).
- The first length, 3, selects ([0] 1 2) 3 4
(where parentheses indicate the sublist to be reversed).
- After reversing that section (0 1 2 into 2 1 0),
we get ([2] 1 0) 3 4.
- Then, the current position moves forward by the length, 3,
plus the skip size, 0: 2 1 0 [3] 4.
Finally, the skip size increases to 1.
- The second length, 4, selects a section which wraps: 2 1) 0 ([3] 4.
- The sublist 3 4 2 1 is reversed to form 1 2 4 3:
4 3) 0 ([1] 2.
- The current position moves forward by the length plus the skip size,
a total of 5, causing it not to move because it wraps around:
4 3 0 [1] 2. The skip size increases to 2.
- The third length, 1, selects a sublist of a single element,
and so reversing it has no effect.
- The current position moves forward by the length (1) plus the skip size (2):
4 [3] 0 1 2. The skip size increases to 3.
- The fourth length, 5, selects every element starting with the second:
4) ([3] 0 1 2. Reversing this sublist (3 0 1 2 4 into 4 2 1 0 3) produces:
3) ([4] 2 1 0.
- Finally, the current position moves forward by 8:
3 4 2 1 [0]. The skip size increases to 4.
In this example, the first two numbers in the list end up being 3 and 4; to
check the process, you can multiply them together to produce 12.
However, you should instead use the standard list size of 256 (with values 0 to
255) and the sequence of lengths in your puzzle input. Once this process is
complete, what is the result of multiplying the first two numbers in the list?
--- Part Two ---
The logic you've constructed forms a single round of the Knot Hash algorithm;
running the full thing requires many of these rounds. Some input and output
processing is also required.
First, from now on, your input should be taken not as a list of numbers, but as
a string of bytes instead. Unless otherwise specified, convert characters to
bytes using their ASCII codes. This will allow you to handle arbitrary ASCII
strings, and it also ensures that your input lengths are never larger than 255.
For example, if you are given 1,2,3, you should convert it to the ASCII codes
for each character: 49,44,50,44,51.
Once you have determined the sequence of lengths to use, add the following
lengths to the end of the sequence: 17, 31, 73, 47, 23. For example, if you are
given 1,2,3, your final sequence of lengths should be
49,44,50,44,51,17,31,73,47,23 (the ASCII codes from the input string combined
with the standard length suffix values).
Second, instead of merely running one round like you did above, run a total of
64 rounds, using the same length sequence in each round. The current position
and skip size should be preserved between rounds. For example, if the previous
example was your first round, you would start your second round with the same
length sequence (3, 4, 1, 5, 17, 31, 73, 47, 23, now assuming they came from
ASCII codes and include the suffix), but start with the previous round's current
position (4) and skip size (4).
Once the rounds are complete, you will be left with the numbers from 0 to 255 in
some order, called the sparse hash. Your next task is to reduce these to a list
of only 16 numbers called the dense hash. To do this, use numeric bitwise XOR to
combine each consecutive block of 16 numbers in the sparse hash (there are 16
such blocks in a list of 256 numbers). So, the first element in the dense hash
is the first sixteen elements of the sparse hash XOR'd together, the second
element in the dense hash is the second sixteen elements of the sparse hash
XOR'd together, etc.
For example, if the first sixteen elements of your sparse hash are as shown
below, and the XOR operator is ^, you would calculate the first output number
like this:
65 ^ 27 ^ 9 ^ 1 ^ 4 ^ 3 ^ 40 ^ 50 ^ 91 ^ 7 ^ 6 ^ 0 ^ 2 ^ 5 ^ 68 ^ 22 = 64
Perform this operation on each of the sixteen blocks of sixteen numbers in your
sparse hash to determine the sixteen numbers in your dense hash.
Finally, the standard way to represent a Knot Hash is as a single hexadecimal
string; the final output is the dense hash in hexadecimal notation. Because each
number in your dense hash will be between 0 and 255 (inclusive), always
represent each number as two hexadecimal digits (including a leading zero as
necessary). So, if your first three numbers are 64, 7, 255, they correspond to
the hexadecimal numbers 40, 07, ff, and so the first six characters of the hash
would be 4007ff. Because every Knot Hash is sixteen such numbers, the
hexadecimal representation is always 32 hexadecimal digits (0-f) long.
Here are some example hashes:
- The empty string becomes a2582a3a0e66e6e86e3812dcb672a272.
- AoC 2017 becomes 33efeb34ea91902bb2f59c9920caa6cd.
- 1,2,3 becomes 3efbe78a8d82f29979031a4aa0b16a9d.
- 1,2,4 becomes 63960835bcdc130f0b66d7ff4f6a5a8e.
Treating your puzzle input as a string of ASCII characters, what is the Knot
Hash of your puzzle input? Ignore any leading or trailing whitespace you might
encounter.
|-}
module Day10 where
import Protolude
import qualified Data.Map.Strict as Map
import Text.Parsec
testInput :: AppState
testInput = mkAppState 5 [3,4,1,5]
input :: AppState
input = mkAppState 256 [187,254,0,81,169,219,1,190,19,102,255,56,46,32,2,216]
mkAppState :: Int -> [Int] -> AppState
mkAppState n ls = AppState [0..(n-1)] n ls 0 0
data AppState = AppState { lst :: [Int]
, lstSize :: Int
, lenghts :: [Int]
, position :: Int
, skip :: Int
} deriving (Show)
oneStep :: AppState -> AppState
oneStep appState@(AppState _ _ [] _ _) = appState
oneStep (AppState lst lstSize (ln:ls) position skip) =
let loop = cycle lst & drop position & take ln
newLst = merge lst (reverse loop) lstSize ln position
in AppState newLst lstSize ls ((position + ln + skip) `rem` lstSize) (skip + 1)
merge :: [Int] -> [Int] -> Int -> Int -> Int -> [Int]
merge lst1 lst2 lst1Size lst2Size n =
let depassement = lst2Size + n - lst1Size in
if depassement > 0
then
let start = drop (lst1Size - n) lst2
mid = take (lst1Size - lst2Size) (drop depassement lst1)
in take lst1Size (start ++ mid ++ lst2)
else take n lst1 ++ lst2 ++ drop (n + lst2Size) lst1
solution1 :: AppState -> Int
solution1 appState =
if null (lenghts appState)
then lst appState & take 2 & foldl' (*) 1
else solution1 (oneStep appState)

View file

@ -131,7 +131,7 @@ parseGroups2 = runParser groups2 0 "Groups 2"
groups2 :: Parsec Text Int Int
groups2 = do
c <- char '{'
(groups2 <|> garbage2) `sepBy` (char ',')
(groups2 <|> garbage2) `sepBy` char ','
c <- char '}'
getState

View file

@ -10,6 +10,7 @@ import qualified Day5
import qualified Day6
import qualified Day7
import qualified Day8
import qualified Day10
main :: IO ()
@ -80,4 +81,8 @@ main = defaultMain $
, testCase "example problem 1" $
Day8.solution2 Day8.testInstructions @?= 10
]
, testGroup "Day 10"
[ testCase "example 1" $
Day10.solution1 Day10.testInput @?= 12
]
]