Advent of Code 2016, Day 4: Security Through Obscurity

#ruby #advent of code 2016

Part A

On Day 4 we are dealing with checksums. We are given strings describing rooms. Each string containts multiple parts: name, sector id and checksum. Our task is to verify which strings are valid by calculating checksum and comparing it with the checksum in the string.

Our input looks like this:

aaaaa-bbb-z-y-x-123[abxyz]
a-b-c-d-e-f-g-h-987[abcde]
not-a-real-room-404[oarel]
totally-real-room-200[decoy]

To calculate checksum we need to count occurences of characters and then sort them in the descending order. In case of ties we need to sort alphabetically. For first example string we have 5 times ‘a’, 3 times ‘b’, 1 time ‘x’, 1 time ‘y’ and 1 time ‘z’. The checksum should be abxyz and it is so the string is valid.

Here is my solution:

data = File.readlines("4.txt").map(&:strip)
sum = 0

data.each do |room|
  name, sector, checksum = room.match(/([a-z\-]+)-(\d+)\[(.+)\]/).captures
  name.gsub!("-", "")

  chars = Hash.new { |hash, key| hash[key] = 0 }
  name.chars.each { |char| chars[char] += 1 }

  sorted = chars.to_a.sort do |a, b|
    res = b[1] <=> a[1]

    if res == 0
      a[0] <=> b[0]
    else
      res
    end
  end

  calculated = sorted[0..4].map { |elem| elem[0] }.join

  if calculated == checksum
    sum += sector.to_i
  end
end

puts sum

In the sort block we are first trying to compare both items by their occurence count. We use Ruby spaceship <=> operator and it case of equal values it will return 0. So if we get a 0 we need to them compare them alphabetically.

Part B

In second part we need to decrypt the names of rooms, so we are interested in the first part of the string and the sector id. Strings are encrypted with kind of Caesar cipher with sector id being an offset. We just need to iterate over each char and shift it given amount of times. Here is my solution:

data = File.readlines("4.txt", chomp: true)

data.each do |room|
  name, sector, checksum = room.match(/([a-z\-]+)-(\d+)\[(.+)\]/).captures

  decrypted = name.chars.map do |char|
    if char == "-"
      " "
    else
      value = char.ord - 'a'.ord
      value = ((value + sector.to_i) % 26) + 'a'.ord

      value.chr
    end
  end.join

  if decrypted =~ /north/
    puts sector
  end
end

This part value = char.ord - 'a'.ord converts any char into alphabet index starting from 0. ‘a’ is 0, ‘b’ is 1, ‘c’ is 2 and so on. It is easier to shift integers as we can just add to it. Them we can convert it back into character by adding 'a'.ord and calling value.chr.