Day 9

This day’s problem is based on simple coordinate geometry.

Parse the input data first (pairs of numbers separated by commas, one pair per line):

import           Data.List.Split (splitOn)

type Point = [Integer]

parse :: String -> [Point]
parse = map (map read . splitOn ",") . lines

Onto the problem itself.

Part 1

This part requires us to simply find a rectangle with the largest area among the points given to us, so first write a method to compute areas given a point. Remember that our representation of a point is [x, y]:

area :: Point -> Point -> Integer
area [x, y] [x', y'] = 
    (1 + abs (x - x')) * (1 + abs (y - y'))

Thus, the solution to the first part is given by the following list comprehension:

p1 :: [Point] -> Integer
p1 poly = maximum [area p p' | p <- poly, p' <- poly]

Part 2

This part is a whole lot trickier. We now require only rectangles that fall within the polygon. Rectangles that have areas outside the polygon should be ignored. The methodology here is to identify rectangles that do not intersect in any way with the polygon itself. Sharing an edge with the polygon is alright however.

Luckily, the intersections are quite easily calculated, since all lines of the polygon are oriented along the axes, and so are the lines forming the rectangle. If a line of the polygon falls to the left of the leftmost line of the rectangle, or to the right of the rightmost line, or above the topmost line, or below the bottommost line, then the line does not intersect the rectangle:

intersects :: Point -> Point -> [Point] -> Bool
intersects [x, y] [x', y'] = not . all away . pairs
  where
    pairs (p : ps) = zip (p : ps) (ps ++ [p])
    away ([lx, ly], [lx', ly']) =
      (max lx lx' <= min x x')
        || (min lx lx' >= max x x')
        || (max ly ly' <= min y y')
        || (min ly ly' >= max y y')

To explain a bit further, we define away according the the rules above. Then the intersects method is given by simply ensuring that of all pairs of points in the polygon (we need pairs to compute line segments of the polygon), we have atleast one line segment that is not away from the rectangle.

Thus, the solution to the second part is a similar list comprehension with an additional guard:

p2 :: [Point] -> Integer
p2 poly =
  maximum
    [ area p p'
    | p <- poly,
      p' <- poly,
      not (intersects p p' poly)
    ]

Finally, a main function to wrap it all up:

main = do
  n <- parse <$> getContents
  print $ p1 n
  print $ p2 n