day 10 solution 1
This commit is contained in:
parent
b613bf7e45
commit
ebcb071e98
6 changed files with 201 additions and 1 deletions
|
@ -37,6 +37,7 @@ library
|
||||||
Day7
|
Day7
|
||||||
Day8
|
Day8
|
||||||
Day9
|
Day9
|
||||||
|
Day10
|
||||||
other-modules:
|
other-modules:
|
||||||
Paths_adventofcode
|
Paths_adventofcode
|
||||||
build-depends:
|
build-depends:
|
||||||
|
|
|
@ -15,6 +15,7 @@ import qualified Day6
|
||||||
import qualified Day7
|
import qualified Day7
|
||||||
import qualified Day8
|
import qualified Day8
|
||||||
import qualified Day9
|
import qualified Day9
|
||||||
|
import qualified Day10
|
||||||
|
|
||||||
showSol :: [Char] -> Doc -> IO ()
|
showSol :: [Char] -> Doc -> IO ()
|
||||||
showSol txt d = putText . toS . render $
|
showSol txt d = putText . toS . render $
|
||||||
|
@ -35,6 +36,7 @@ solutions = Map.fromList [(["1"], day1)
|
||||||
,(["7"], day7)
|
,(["7"], day7)
|
||||||
,(["8"], day8)
|
,(["8"], day8)
|
||||||
,(["9"], day9)
|
,(["9"], day9)
|
||||||
|
,(["10"], day10)
|
||||||
]
|
]
|
||||||
|
|
||||||
day1 :: IO ()
|
day1 :: IO ()
|
||||||
|
@ -95,3 +97,9 @@ day9 = do
|
||||||
showSol "Solution 1" (int sol1)
|
showSol "Solution 1" (int sol1)
|
||||||
sol2 <- Day9.solution2
|
sol2 <- Day9.solution2
|
||||||
showSol "Solution 2" (int sol2)
|
showSol "Solution 2" (int sol2)
|
||||||
|
|
||||||
|
day10 :: IO ()
|
||||||
|
day10 = do
|
||||||
|
putText "Day 10:"
|
||||||
|
let sol1 = Day10.solution1 Day10.input
|
||||||
|
showSol "Solution 1" (int sol1)
|
||||||
|
|
|
@ -22,6 +22,7 @@ library:
|
||||||
- Day7
|
- Day7
|
||||||
- Day8
|
- Day8
|
||||||
- Day9
|
- Day9
|
||||||
|
- Day10
|
||||||
dependencies:
|
dependencies:
|
||||||
- base >=4.7 && <5
|
- base >=4.7 && <5
|
||||||
- protolude
|
- protolude
|
||||||
|
|
185
src/Day10.hs
Normal file
185
src/Day10.hs
Normal 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)
|
|
@ -131,7 +131,7 @@ parseGroups2 = runParser groups2 0 "Groups 2"
|
||||||
groups2 :: Parsec Text Int Int
|
groups2 :: Parsec Text Int Int
|
||||||
groups2 = do
|
groups2 = do
|
||||||
c <- char '{'
|
c <- char '{'
|
||||||
(groups2 <|> garbage2) `sepBy` (char ',')
|
(groups2 <|> garbage2) `sepBy` char ','
|
||||||
c <- char '}'
|
c <- char '}'
|
||||||
getState
|
getState
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import qualified Day5
|
||||||
import qualified Day6
|
import qualified Day6
|
||||||
import qualified Day7
|
import qualified Day7
|
||||||
import qualified Day8
|
import qualified Day8
|
||||||
|
import qualified Day10
|
||||||
|
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
|
@ -80,4 +81,8 @@ main = defaultMain $
|
||||||
, testCase "example problem 1" $
|
, testCase "example problem 1" $
|
||||||
Day8.solution2 Day8.testInstructions @?= 10
|
Day8.solution2 Day8.testInstructions @?= 10
|
||||||
]
|
]
|
||||||
|
, testGroup "Day 10"
|
||||||
|
[ testCase "example 1" $
|
||||||
|
Day10.solution1 Day10.testInput @?= 12
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue