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]"
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]" ]]
In [8]:
-- Solution of part 1
sum . map sectorId . filter isRoom . map parseRoom <$> inputLines
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