require_relative 'util' # --- Day 4: Security Through Obscurity --- # Finally, you come across an information kiosk with a list of # rooms. Of course, the list is encrypted and full of decoy data, but # the instructions to decode the list are barely hidden nearby. Better # remove the decoy data first. # Each room consists of an encrypted name (lowercase letters separated # by dashes) followed by a dash, a sector ID, and a checksum in square # brackets. # A room is real (not a decoy) if the checksum is the five most common # letters in the encrypted name, in order, with ties broken by # alphabetization. For example: # - `aaaaa-bbb-z-y-x-123[abxyz]` is a real room because the most # common letters are a (5), b (3), and then a tie between x, y, and # z, which are listed alphabetically. # - `a-b-c-d-e-f-g-h-987[abcde]` is a real room because although the # letters are all tied (1 of each), the first five are listed # alphabetically. # - `not-a-real-room-404[oarel]` is a real room. # - `totally-real-room-200[decoy]` is not. # Of the real rooms from the list above, the sum of their sector IDs # is 1514. # What is the sum of the sector IDs of the real rooms? input = File.open('04.txt') { |f| f.readlines.map(&:chomp) } def frequencies(string) result = Hash.new { |h, k| h[k] = 0 } string.each_char { |char| result[char] += 1 } result end def parse(line) line[/^(.*)-(\d+)\[(\w+)\]$/] [$1, $2.to_i, $3] end def real_room?(name, checksum) histogram = frequencies(name.delete('-0123456789')).sort_by { |k, v| [-v, k] } histogram.take(5).map { |c, _| c }.join == checksum end assert(real_room?('aaaaa-bbb-z-y-x', 'abxyz')) assert(real_room?('a-b-c-d-e-f-g-h-987', 'abcde')) assert(real_room?('not-a-real-room-404', 'oarel')) assert(!real_room?('totally-real-room-200', 'decoy')) def easy(input) sum = 0 input.each do |line| name, id, checksum = parse(line) sum += id if real_room?(name, checksum) end sum end puts "easy(input): #{easy(input)}" # --- Part Two --- # With all the decoy data out of the way, it's time to decrypt this # list and get moving. # The room names are encrypted by a state-of-the-art shift cipher, # which is nearly unbreakable without the right software. However, the # information kiosk designers at Easter Bunny HQ were not expecting to # deal with a master cryptographer like yourself. # To decrypt a room name, rotate each letter forward through the # alphabet a number of times equal to the room's sector ID. A becomes # B, B becomes C, Z becomes A, and so on. Dashes become spaces. # For example, the real name for `qzmt-zixmtkozy-ivhz-343` is `very # encrypted name`. # What is the sector ID of the room where North Pole objects are # stored? CHARSET = 'abcdefghijklmnopqrstuvwxyz'.freeze def decrypt(string, rotations) output = string.chars.map do |c| CHARSET.include?(c) ? ('a'.ord + (c.ord - 'a'.ord + rotations) % 26).chr : c end output.join.tr('-', ' ') end def hard(input) input.each do |line| name, id, checksum = parse(line) next unless real_room?(name, checksum) return id if decrypt(name, id)[/north.*?pole.*?storage/] end end assert(decrypt('qzmt-zixmtkozy-ivhz', 343) == 'very encrypted name') puts "hard(input): #{hard(input)}"