Day 12: Garden Groups
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
Haskell
Detecting regions is a floodfill. For Part 2, I select all adjacent tiles that are not part of a region and group them by the direction relative to the closest region tile, then group adjacent tiles with the same direction again and count.
Edit:
Takes 0.06s
Reveal Code
import Control.Arrow import Data.Array.Unboxed (UArray) import Data.Set (Set) import Data.Map (Map) import qualified Data.List as List import qualified Data.Set as Set import qualified Data.Map as Map import qualified Data.Array.Unboxed as UArray parse :: String -> UArray (Int, Int) Char parse s = UArray.listArray ((1, 1), (n, m)) . filter (/= '\n') $ s where n = takeWhile (/= '\n') >>> length $ s m = filter (== '\n') >>> length >>> pred $ s neighborCoordinates (p1, p2) = [(p1-1, p2), (p1, p2-1), (p1, p2+1), (p1+1, p2)] allNeighbors p a = neighborCoordinates >>> filter (UArray.inRange (UArray.bounds a)) $ p regionNeighbors p a = allNeighbors p >>> filter ((a UArray.!) >>> (== pTile)) $ a where pTile = a UArray.! p floodArea :: Set (Int, Int) -> Set (Int, Int) -> UArray (Int, Int) Char -> Set (Int, Int) floodArea e o a | Set.null o = e | otherwise = floodArea e' o' a where e' = Set.union e o o' = Set.fold (Set.union . Set.fromDistinctAscList . (filter (`Set.notMember` e')) . (flip regionNeighbors a)) Set.empty o findRegions garden = findRegions' (Set.fromList . UArray.indices $ garden) garden findRegions' remainingIndices garden | Set.null remainingIndices = [] | otherwise = removedIndices : findRegions' remainingIndices' garden where removedIndices = floodArea Set.empty (Set.singleton . Set.findMin $ remainingIndices) garden remainingIndices' = Set.difference remainingIndices removedIndices perimeter region = Set.fold ((+) . length . filter (`Set.notMember` region) . neighborCoordinates) 0 region part1 rs = map (Set.size &&& perimeter) >>> map (uncurry (*)) >>> sum $ rs turnLeft ( 0, 1) = (-1, 0) -- right turnLeft ( 0,-1) = ( 1, 0) -- left turnLeft ( 1, 0) = ( 0, 1) -- down turnLeft (-1, 0) = ( 0,-1) -- up turnRight = turnLeft . turnLeft . turnLeft move (py, px) (dy, dx) = (py + dy, px + dx) tupleDelta (y1, x1) (y2, x2) = (y1-y2, x1-x2) isRegionInner region p = all (`Set.member` region) (neighborCoordinates p) groupEdges d ps | Set.null ps = [] | otherwise = collectedEdge : groupEdges d ps' where ps' = Set.difference ps collectedEdge collectedEdge = Set.union leftPoints rightPoints leftPoints = iterate (move dl) >>> takeWhile (`Set.member` ps) >>> Set.fromList $ currentPoint rightPoints = iterate (move dr) >>> takeWhile (`Set.member` ps) >>> Set.fromList $ currentPoint currentPoint = Set.findMin ps dr = turnRight d dl = turnLeft d linearPerimeter region = Map.foldr ((+) . length) 0 $ groupedEdges where edgeTiles = Set.filter (not . isRegionInner region) region regionNeighbors = List.concatMap (\ p -> map (p,). filter (`Set.notMember` region) . neighborCoordinates $ p) . Set.toList $ region groupedNeighbors = List.map (uncurry tupleDelta &&& Set.singleton . snd) >>> Map.fromListWith (Set.union) $ regionNeighbors groupedEdges = Map.mapWithKey groupEdges $ groupedNeighbors part2 rs = map (Set.size &&& linearPerimeter) >>> map (uncurry (*)) >>> sum $ rs main = getContents >>= print . (part1 &&& part2) . findRegions . parse