Last updated: 2017-12-11
Code version: d97ce22
sessionInfo()
R version 3.4.2 (2017-09-28)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Sierra 10.12.6
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib
locale:
[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] withr_2.1.0 rlist_0.4.6.1 jsonlite_1.5 bindrcpp_0.2 aocodeR_0.1.1 testthat_1.0.2
[7] forcats_0.2.0 stringr_1.2.0 dplyr_0.7.4 purrr_0.2.4 readr_1.1.1 tidyr_0.7.2
[13] tibble_1.3.4 ggplot2_2.2.1 tidyverse_1.2.1
loaded via a namespace (and not attached):
[1] reshape2_1.4.2 haven_1.1.0 lattice_0.20-35 colorspace_1.3-2 htmltools_0.3.6
[6] base64enc_0.1-3 yaml_2.1.15 rlang_0.1.4 foreign_0.8-69 glue_1.2.0
[11] modelr_0.1.1 readxl_1.0.0 bindr_0.1 plyr_1.8.4 munsell_0.4.3
[16] gtable_0.2.0 cellranger_1.1.0 rvest_0.3.2 evaluate_0.10.1 psych_1.7.8
[21] knitr_1.17 curl_3.0 parallel_3.4.2 broom_0.4.2 Rcpp_0.12.14
[26] scales_0.5.0 backports_1.1.1 mnormt_1.5-5 digest_0.6.12 hms_0.4.0
[31] stringi_1.1.6 grid_3.4.2 rprojroot_1.2 cli_1.0.0 tools_3.4.2
[36] magrittr_1.5 lazyeval_0.2.1 crayon_1.3.4 pkgconfig_2.0.1 data.table_1.10.4-3
[41] rsconnect_0.8.5 xml2_1.1.1 lubridate_1.7.1 assertthat_0.2.0 rmarkdown_1.8
[46] httr_1.3.1 rstudioapi_0.7 R6_2.2.2 nlme_3.1-131 compiler_3.4.2
[51] git2r_0.19.0
— Day 10: Knot Hash —
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.
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.
[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
into2 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
(4,5,1,2)
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?
library(tidyverse)
library(testthat)
library(aocodeR)
input <- aoc_get_input(day = 10, cookie_path = paste0(rprojroot::find_rstudio_root_file(),
"/secrets/session_cookie.txt"))
input
[1] "130,126,1,11,140,2,255,207,18,254,246,164,29,104,0,224"
knot <- function(l.input = input, v = 0:255,
skip = 0, cp = 1, cycles = 1, hex = F) {
if(hex){l.input <- l.input %>% hex.l} else{
l.input <- l.input %>% strsplit(",") %>% unlist %>% as.numeric
}
# intitialise algorithm
lv <- length(v)
for(i in rep(1:length(l.input), cycles)){
l <- l.input[i]
# twist
tc<- cp:(cp - 1 + l) %% lv %>% recode(`0` = lv)
v[tc] <- v[rev(tc)]
# move cp
cp <- (cp + l + skip) %% lv
if (cp == 0){cp <- lv} #fix %% 0s
# update params
skip <- skip + 1
i <- i + 1
}
v
}
expect_equal(knot(l.input = "3,4,1,5", v = 0:4), c(3, 4, 2, 1, 0))
knot(l.input = input) %>% head(2) %>% prod()
[1] 38628
— 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.
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
.
Add to end of sequece: 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).
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.
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.
I got really confused by part 2. I couldn’t understand what exactly I needed to do to xor
a bunch of numbers! I was especially confused by the instruction in the brief to xor
the values together.
After alot of YouTube video watching I’d determined what was to be xor
ed were the vectors of binary encoding of each number (ie the bitwise comparison bit) and found bitwXor()
to accomplish it. What I hadn’t understood was that each number needed to compare sequentially to the next one in the sequence so, although I came across reduce()
I couldn’t work out how to correctly implement because I was confused about the task! Eventually in the evening I gave in and had a look at @fmichonneau and @exunckly_twitter’s responses in or AoC #rstats gitter channel!
So close! @exunckly_twitter vectorxor function really clarified what the actual task was, ie how the 16 numbers are reduced to 1, and @fmichonneau’s solution showed me how reduce
worked. So I finally got my solution by patching together those insights into mine.
I guess that’s somewhat cheating 😬 but I’d reached the limit of wracking my brains on AoC for this weekend! Did learn a lot however about computing in general and nice to understand the approaches that go into cryptography.
hex.l <- function(input= "1,2,3") {
c(utf8ToInt(input) , c(17, 31, 73, 47, 23))
}
hash <- function(input, ...) {
input %>%
knot(v = 0:255, cycles = 64, hex = T) %>%
split(., ceiling(seq_along(.)/16)) %>%
map_int(~reduce(.x, bitwXor)) %>%
as.hexmode %>%
paste(collapse = "")
}
expect_equal(utf8ToInt("1,2,3"),c(49, 44, 50, 44, 51))
expect_equal("65 ^ 27 ^ 9 ^ 1 ^ 4 ^ 3 ^ 40 ^ 50 ^ 91 ^ 7 ^ 6 ^ 0 ^ 2 ^ 5 ^ 68 ^ 22" %>%
strsplit(., " \\^ ") %>% unlist %>% as.numeric %>% reduce(., bitwXor), 64)
expect_equal(hash("1,2,3"),"3efbe78a8d82f29979031a4aa0b16a9d")
expect_equal(hash("AoC 2017"),"33efeb34ea91902bb2f59c9920caa6cd")
expect_equal(hash("1,2,4"),"63960835bcdc130f0b66d7ff4f6a5a8e")
hash(input)
[1] "e1462100a34221a7f0906da15c1c979a"