{-# Language ImportQualifiedPost #-}
{-|
Module      : Main
Description : Day 2 solution
Copyright   : (c) Eric Mertens, 2018
License     : ISC
Maintainer  : emertens@gmail.com

<https://adventofcode.com/2018/day/2>

Day 2 has us count up the occurrences of characters in words and
find pairs of characters that match except for a single position.

-}
module Main (main) where

import Advent (counts, countBy, getInputLines)
import Data.List (tails)
import Data.Map (Map)
import Data.Map qualified as Map

-- | Print the answers to part 1 and 2 of day 2's task.
--
-- >>> :main
-- 8398
-- hhvsdkatysmiqjxunezgwcdpr
main :: IO ()
IO ()
main =
  do [String]
inp <- Int -> Int -> IO [String]
getInputLines Int
2018 Int
2
     Int -> IO ()
forall a. Show a => a -> IO ()
print ([String] -> Int
forall a. Ord a => [[a]] -> Int
part1 [String]
inp)
     String -> IO ()
putStrLn ([String] -> String
forall a. Eq a => [[a]] -> [a]
part2 [String]
inp)

-- | Compute the number of elements in the list that have exactly 2 of
-- a particular element with the number of elements in the list that have
-- exactly 3 of a particular number of elements.
--
-- >>> part1 ["abcdef", "bababc", "abbcde", "abcccd", "aabcdd", "abcdee", "ababab"]
-- 12
part1 :: Ord a => [[a]] -> Int
part1 :: forall a. Ord a => [[a]] -> Int
part1 [[a]]
inp = [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
product ((Int -> Int) -> [Int] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Int -> Int
exact [Int
2,Int
3])
  where
    cards :: [Map a Int]
cards = ([a] -> Map a Int) -> [[a]] -> [Map a Int]
forall a b. (a -> b) -> [a] -> [b]
map [a] -> Map a Int
forall (f :: * -> *) a. (Foldable f, Ord a) => f a -> Map a Int
counts [[a]]
inp
    exact :: Int -> Int
exact Int
n = (Map a Int -> Bool) -> [Map a Int] -> Int
forall (f :: * -> *) a. Foldable f => (a -> Bool) -> f a -> Int
countBy (Int -> Map a Int -> Bool
forall a. Eq a => a -> Map a a -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem Int
n) [Map a Int]
cards

-- | Return the common elements between the two entries in the given list
-- where all but one of the elements are equal.
--
-- >>> part2 ["abcde", "fghij", "klmno", "pqrst", "fguij", "axcye", "wvxyz"]
-- "fgij"
part2 :: Eq a => [[a]] -> [a]
part2 :: forall a. Eq a => [[a]] -> [a]
part2 [[a]]
inp = [[a]] -> [a]
forall a. HasCallStack => [a] -> a
head [[a]
r | [a]
x:[[a]]
xs <- [[a]] -> [[[a]]]
forall a. [a] -> [[a]]
tails [[a]]
inp, [a]
y <- [[a]]
xs, Just [a]
r <- [[a] -> [a] -> Maybe [a]
forall a. Eq a => [a] -> [a] -> Maybe [a]
offbyone [a]
x [a]
y]]

-- | Find the common elements between two lists that differ in exactly one
-- position.
--
-- >>> offbyone "abcde" "axcye"
-- Nothing
-- >>> offbyone "fghij" "fguij"
-- Just "fgij"
offbyone :: Eq a => [a] -> [a] -> Maybe [a]
offbyone :: forall a. Eq a => [a] -> [a] -> Maybe [a]
offbyone (a
x:[a]
xs) (a
y:[a]
ys)
  | a
x  a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
y  = (a
x a -> [a] -> [a]
forall a. a -> [a] -> [a]
:) ([a] -> [a]) -> Maybe [a] -> Maybe [a]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [a] -> [a] -> Maybe [a]
forall a. Eq a => [a] -> [a] -> Maybe [a]
offbyone [a]
xs [a]
ys
  | [a]
xs [a] -> [a] -> Bool
forall a. Eq a => a -> a -> Bool
== [a]
ys = [a] -> Maybe [a]
forall a. a -> Maybe a
Just [a]
xs
offbyone [a]
_ [a]
_ = Maybe [a]
forall a. Maybe a
Nothing