{-# Language QuasiQuotes, ParallelListComp #-}
{-|
Module      : Main
Description : Day 5 solution
Copyright   : (c) Eric Mertens, 2021
License     : ISC
Maintainer  : emertens@gmail.com

<https://adventofcode.com/2021/day/5>

The input is a bunch of segments; count intersections.

-}
module Main (main) where

import Advent (counts, countBy, format)

-- | >>> :main
-- 8060
-- 21577
main :: IO ()
IO ()
main =
 do [(Int, Int, Int, Int)]
inp <- [format|2021 5 (%u,%u -> %u,%u%n)*|]
    Int -> IO ()
forall a. Show a => a -> IO ()
print ([(Int, Int, Int, Int)] -> Int
solve (((Int, Int, Int, Int) -> Bool)
-> [(Int, Int, Int, Int)] -> [(Int, Int, Int, Int)]
forall a. (a -> Bool) -> [a] -> [a]
filter (Int, Int, Int, Int) -> Bool
isStraight [(Int, Int, Int, Int)]
inp))
    Int -> IO ()
forall a. Show a => a -> IO ()
print ([(Int, Int, Int, Int)] -> Int
solve [(Int, Int, Int, Int)]
inp)

-- | Compute the number of points covered by more than one segment
solve :: [(Int, Int, Int, Int)] -> Int
solve :: [(Int, Int, Int, Int)] -> Int
solve = (Int -> Bool) -> Map (Int, Int) Int -> Int
forall (f :: * -> *) a. Foldable f => (a -> Bool) -> f a -> Int
countBy (Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
1) (Map (Int, Int) Int -> Int)
-> ([(Int, Int, Int, Int)] -> Map (Int, Int) Int)
-> [(Int, Int, Int, Int)]
-> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(Int, Int)] -> Map (Int, Int) Int
forall (f :: * -> *) a. (Foldable f, Ord a) => f a -> Map a Int
counts ([(Int, Int)] -> Map (Int, Int) Int)
-> ([(Int, Int, Int, Int)] -> [(Int, Int)])
-> [(Int, Int, Int, Int)]
-> Map (Int, Int) Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Int, Int, Int, Int) -> [(Int, Int)])
-> [(Int, Int, Int, Int)] -> [(Int, Int)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Int, Int, Int, Int) -> [(Int, Int)]
points

-- | Predicate for straight segments
isStraight :: (Int, Int, Int, Int) -> Bool
isStraight :: (Int, Int, Int, Int) -> Bool
isStraight (Int
x1, Int
y1, Int
x2, Int
y2) = Int
x1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
x2 Bool -> Bool -> Bool
|| Int
y1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
y2

-- | Enumerate the points contained in a segment
--
-- >>> points (1,1,1,3)
-- [(1,1),(1,2),(1,3)]
--
-- >>> points (9,7,7,7)
-- [(9,7),(8,7),(7,7)]
--
-- >>> points (1,1,3,3)
-- [(1,1),(2,2),(3,3)]
--
-- >>> points (9,7,7,9)
-- [(9,7),(8,8),(7,9)]
points :: (Int, Int, Int, Int) -> [(Int, Int)]
points :: (Int, Int, Int, Int) -> [(Int, Int)]
points (Int
x1, Int
y1, Int
x2, Int
y2)
  | Int
x1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
x2  = [(Int
x1,Int
y) | Int
y <- Int -> Int -> [Int]
range Int
y1 Int
y2]
  | Int
y1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
y2  = [(Int
x,Int
y1) | Int
x <- Int -> Int -> [Int]
range Int
x1 Int
x2]
  | Bool
otherwise = [(Int
x,Int
y)  | Int
x <- Int -> Int -> [Int]
range Int
x1 Int
x2 | Int
y <- Int -> Int -> [Int]
range Int
y1 Int
y2]

-- | Inclusive enumeration of the integers between two bounds
--
-- >>> range 3 5
-- [3,4,5]
--
-- >>> range 9 9
-- [9]
--
-- >>> range 7 1
-- [7,6,5,4,3,2,1]
range :: Int -> Int -> [Int]
range :: Int -> Int -> [Int]
range Int
x Int
y
  | Int
x Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
y    = [Int
x .. Int
y]
  | Bool
otherwise = [Int
x, Int
xInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1 .. Int
y]