diff --git a/adventofcode.cabal b/adventofcode.cabal index e7c7e6f..8c583a3 100644 --- a/adventofcode.cabal +++ b/adventofcode.cabal @@ -37,6 +37,7 @@ library Day7 Day8 Day9 + Day10 other-modules: Paths_adventofcode build-depends: diff --git a/app/Main.hs b/app/Main.hs index 5780073..c88cb40 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -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) diff --git a/package.yaml b/package.yaml index 6c0e223..cc1c044 100644 --- a/package.yaml +++ b/package.yaml @@ -22,6 +22,7 @@ library: - Day7 - Day8 - Day9 + - Day10 dependencies: - base >=4.7 && <5 - protolude diff --git a/src/Day10.hs b/src/Day10.hs new file mode 100644 index 0000000..17f2d98 --- /dev/null +++ b/src/Day10.hs @@ -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) diff --git a/src/Day9.hs b/src/Day9.hs index be5ace2..03859b7 100644 --- a/src/Day9.hs +++ b/src/Day9.hs @@ -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 diff --git a/test/Spec.hs b/test/Spec.hs index 7086bde..9230d84 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -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 + ] ]