Day 4: Security Through Obscurity


In [1]:
inputLines = lines <$> readFile "input/day4.txt"

In [2]:
import Data.Char (isDigit, isLetter)
import Data.List (group, isInfixOf, sort)

In [3]:
data Room = Room {name :: String, sectorId :: Int, checksum :: String} deriving (Show)

In [4]:
-- ihaskell apparently does not include Haskell modules for regular expressions, so we will parse the room manually
parseRoom :: String -> Room
parseRoom roomString = Room  name sectorId checksum
    where
        name = init $ takeWhile (not . isDigit) roomString -- init drops the dash between name and id
        sectorId = read $ filter isDigit roomString
        checksum =  filter isLetter $ dropWhile (/= '[') roomString

In [5]:
-- Verify that parseRoom works
parseRoom "aaaaa-bbb-z-y-x-123[abxyz]"


Room {name = "aaaaa-bbb-z-y-x", sectorId = 123, checksum = "abxyz"}

In [6]:
-- Part 1: verify if a room is a real room by calculating and comparing the checksum
isRoom :: Room -> Bool
isRoom room = checksum room == calculateChecksum (name room)
    where
        calculateChecksum :: String -> String
        calculateChecksum roomName = 
            take 5 $
            map snd $
            sort $
            map (\g -> (-length g, head g)) $ -- minus sign because frequent letters come first
            group $
            sort $
            filter (/= '-') roomName

In [7]:
-- check that the examples are evaluated correctly
[isRoom $ parseRoom room | 
    room <- [ "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]" ]]


[True,True,True,False]

In [8]:
-- Solution of part 1
sum . map sectorId . filter isRoom . map parseRoom <$> inputLines


278221

In [9]:
-- Part 2: decrypt room names
decryptRoom :: Room -> String
decryptRoom room = map decryptChar $ name room
    where
        decryptChar :: Char -> Char
        decryptChar '-' = ' '
        decryptChar letter = shift code letter
        
        code = sectorId room `mod` 26
        
        shift :: Int -> Char -> Char
        shift 0 = id
        shift n = shift (pred n) . shiftOne
        
        shiftOne :: Char -> Char
        shiftOne 'z' = 'a'
        shiftOne c = succ c

In [10]:
-- Solution of part 2
map sectorId . filter (("northpole" `isInfixOf`) . decryptRoom) . filter isRoom . map parseRoom <$> inputLines


[267]