VegOwOtenks

joined 7 months ago
[โ€“] VegOwOtenks 3 points 2 weeks ago

Haskell

I had several strategy switches from brute-force to pathfinding (when doing part1 input instead of example) because It simply wouldn't finish. My solution only found the first path to the design, which is why I rewrote to only count how many towels there are for each prefix I have already built. Do that until there is either only one entry with the total combinations count or no entry and it's impossible to build the design.

I like the final solution, its small (unlike my other solutions) and runs fast.

๐Ÿš€

import Control.Arrow

import Data.Map (Map)

import qualified Data.List as List
import qualified Data.Map as Map

parse :: String -> ([String], [String])
parse = lines . init
        >>> (map (takeWhile (/= ',')) . words . head &&& drop 2)

countDesignPaths :: [String] -> String -> Map Int Int -> Int
countDesignPaths ts d es
        | Map.null es    = 0
        | ml == length d = mc
        | otherwise = countDesignPaths ts d es''
        where
                ((ml, mc), es') = Map.deleteFindMin es
                ns = List.filter (flip List.isPrefixOf (List.drop ml d))
                        >>> List.map length
                        >>> List.map (ml +)
                        $ ts
                es'' = List.foldl (\ m l' -> Map.insertWith (+) l' mc m) es'
                        $ ns
solve (ts, ds) = List.map (flip (countDesignPaths ts) (Map.singleton 0 1))
        >>> (List.length . List.filter (/= 0) &&& List.sum)
        $ ds

main = getContents
        >>= print
        . solve
        . parse

[โ€“] VegOwOtenks 2 points 2 weeks ago* (last edited 2 weeks ago)

Haskell

Wasn't there a pathfinding problem just recently?

Edit: Optimization to avoid recalculating paths all the time

Haskell with lambdas

import Control.Arrow
import Control.Monad
import Data.Bifunctor hiding (first, second)

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.Maybe as Maybe

parse :: String -> [(Int, Int)]
parse = map (join bimap read) . map (break (== ',') >>> second (drop 1)) . filter (/= "") . lines

lowerBounds = (0, 0)
exitPosition = (70, 70)
initialBytes = 1024

adjacent (py, px) = Set.fromDistinctAscList [(py-1, px), (py, px-1), (py, px+1), (py+1, px)]

data Cost = Wall | Explored Int
        deriving (Show, Eq)

inBounds (py, px)
        | py < 0 = False
        | px < 0 = False
        | py > fst exitPosition = False
        | px > snd exitPosition = False
        | otherwise = True

dijkstra :: Map Int (Set (Int, Int)) -> Map (Int, Int) Cost -> (Int, (Int, Int), Map (Int, Int) Cost)
dijkstra queue walls
        | Map.null queue = (-1, (-1, -1), Map.empty)
        | minPos == exitPosition = (minKey, minPos, walls)
        | Maybe.isJust (walls Map.!? minPos) = dijkstra remainingQueue' walls
        | not . inBounds $ minPos = dijkstra remainingQueue' walls
        | otherwise = dijkstra neighborQueue updatedWalls
        where
                ((minKey, posSet), remainingQueue) = Maybe.fromJust . Map.minViewWithKey $ queue
                (minPos, remainingPosSet) = Maybe.fromJust . Set.minView $ posSet
                remainingQueue' = if not . Set.null $ remainingPosSet then Map.insert minKey remainingPosSet remainingQueue else remainingQueue
                neighborQueue = List.foldl (\ m n -> Map.insertWith (Set.union) neighborKey (Set.singleton n) m) remainingQueue' neighbors
                updatedWalls = Map.insert minPos (Explored minKey) walls
                neighborKey = minKey + 1
                neighbors = adjacent minPos

isExplored :: Cost -> Bool
isExplored Wall = False
isExplored (Explored _) = True

findPath :: Int -> (Int, Int) -> Map (Int, Int) Cost -> [(Int, Int)]
findPath n p ts
        | p == lowerBounds = [lowerBounds]
        | n == 0 = error "Out of steps when tracing backwards"
        | List.null neighbors = error "No matching neighbors when tracing backwards"
        | otherwise = p : findPath (pred n) (fst . head $ neighbors) ts
        where
                neighbors = List.filter ((== Explored (pred n)) . snd) . List.filter (isExplored . snd) . List.map (join (,) >>> second (ts Map.!)) . List.filter inBounds . Set.toList . adjacent $ p

runDijkstra = flip zip (repeat Wall)
        >>> Map.fromList
        >>> dijkstra (Map.singleton 0 (Set.singleton lowerBounds))

fst3 :: (a, b, c) -> a
fst3 (a, _, _) = a

thrd :: (a, b, c) -> c
thrd (_, _, c) = c

part1 = take initialBytes
        >>> runDijkstra
        >>> \ (n, _, _) -> n

firstFailing :: [(Int, Int)] -> [[(Int, Int)]] -> (Int, Int)
firstFailing path (bs:bss)
        | List.last bs `List.notElem` path = firstFailing path bss
        | c == (-1) = List.last bs
        | otherwise = firstFailing (findPath c p ts) bss
        where
                (c, p, ts) = runDijkstra bs

part2 bs = repeat
        >>> zip [initialBytes..length bs]
        >>> map (uncurry take)
        >>> firstFailing path
        $ bs
        where
                (n, p, ts) = runDijkstra . take 1024 $ bs
                path = findPath n p ts

main = getContents
        >>= print
        . (part1 &&& part2)
        . parse

[โ€“] VegOwOtenks 2 points 2 weeks ago

Haskell

Part 2 was tricky, I tried executing the algorithm backwards, which worked fine for the example but not with the input program, because it uses one-way functions .-. Then I tried to write an algorithm that would try all valid combinations of powers of 8 and but I failed, I then did it by hand.

Solution Codeblock

import Control.Arrow
import Data.Bits
import qualified Data.Char as Char
import qualified Data.List as List

replace c r c' = if c' == c then r else c'

parse :: String -> ([Integer], [Int])
parse = map (replace ',' ' ')
        >>> filter ((Char.isDigit &&& Char.isSpace) >>> uncurry (||))
        >>> words
        >>> splitAt 3
        >>> (map read *** map read)

type InstructionPointer = Int

adv = 0
bxl = 1
bst = 2
jnz = 3
bxc = 4
out = 5
bdv = 6
cdv = 7

lookupCombo _    0 = 0
lookupCombo _    1 = 1
lookupCombo _    2 = 2
lookupCombo _    3 = 3
lookupCombo regs 4 = regs !! 0
lookupCombo regs 5 = regs !! 1
lookupCombo regs 6 = regs !! 2
lookupCombo regs 7 = error "Invalid operand"

execute :: InstructionPointer -> [Integer] -> [Int] -> [Int]
execute ip regs@(regA:regB:regC:[]) ops
        | ip >= length ops = []
        | instruction == adv = execute (ip + 2) [regA `div` (2 ^ comboValue), regB, regC] ops
        | instruction == bxl = execute (ip + 2) [regA, xor regB (toInteger operand), regC] ops
        | instruction == bst = execute (ip + 2) [regA, comboValue `mod` 8, regC] ops
        | instruction == jnz && regA == 0 = execute (ip + 2) regs ops
        | instruction == jnz && regA /= 0 = execute operand regs ops
        | instruction == bxc = execute (ip + 2) [regA, xor regB regC, regC] ops
        | instruction == out = (fromIntegral comboValue) `mod` 8 : execute (ip + 2) regs ops
        | instruction == bdv = execute (ip + 2) [regA, regA `div` (2 ^ comboValue), regC] ops
        | instruction == cdv = execute (ip + 2) [regA, regB, regA `div` (2 ^ comboValue)] ops
        where
                (instruction, operand) = (ops !! ip, ops !! (succ ip))
                comboValue             = lookupCombo regs operand

part1 = uncurry (execute 0)
        >>> List.map show
        >>> List.intercalate ","

valid i t n = ((n `div` (8^i)) `mod` 8) `xor` 7 `xor` (n `div` (4*(8^i))) == t

part2 = const 247839653009594

main = getContents
        >>= print
        . (part1 &&& part2)
        . parse
```haskell
[โ€“] VegOwOtenks 3 points 2 weeks ago* (last edited 2 weeks ago)

Haskell

This one was surprisingly slow to run

Big codeblock

import Control.Arrow

import Data.Map (Map)
import Data.Set (Set)
import Data.Array.ST (STArray)
import Data.Array (Array)
import Control.Monad.ST (ST, runST)

import qualified Data.Char as Char
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Array.ST as MutableArray
import qualified Data.Array as Array
import qualified Data.Maybe as Maybe

data Direction = East | West | South | North
        deriving (Show, Eq, Ord)
data MazeTile = Start | End | Wall | Unknown | Explored (Map Direction ExplorationScore)
        deriving Eq

--      instance Show MazeTile where
--              show Wall = "#"
--              show Start = "S"
--              show End = "E"
--              show Unknown = "."
--              show (Explored (East, _))  = ">"
--              show (Explored (South, _)) = "v"
--              show (Explored (West, _))  = "<"
--              show (Explored (North, _)) = "^"

type Position = (Int, Int)
type ExplorationScore = Int

translate '#' = Wall
translate '.' = Unknown
translate 'S' = Start
translate 'E' = End

parse :: String -> Array (Int, Int) MazeTile
parse s = Array.listArray ((1, 1), (height - 1, width)) . map translate . filter (/= '\n') $ s
        where
                width = length . takeWhile (/= '\n') $ s
                height = length . filter (== '\n') $ s

(a1, b1) .+. (a2, b2) = (a1+a2, b1+b2)
(a1, b1) .-. (a2, b2) = (a1-a2, b1-b2)

directions = [East, West, South, North]
directionVector East  = (0,  1)
directionVector West  = (0, -1)
directionVector North = (-1, 0)
directionVector South = ( 1, 0)

turnRight East  = South
turnRight South = West
turnRight West  = North
turnRight North = East

walkableNeighbors a p = do
        let neighbors = List.map ((.+. p) . directionVector) directions
        tiles <- mapM (MutableArray.readArray a) neighbors
        let neighborPosition = List.map fst . List.filter ((/= Wall). snd) . zip neighbors $ tiles
        return $ neighborPosition


findDeadEnds a = Array.assocs
        >>> List.filter (snd >>> (== Unknown))
        >>> List.map (fst)
        >>> List.filter (isDeadEnd a)
        $ a
isDeadEnd a p = List.map directionVector
        >>> List.map (.+. p)
        >>> List.map (a Array.!)
        >>> List.filter (/= Wall)
        >>> List.length
        >>> (== 1)
        $ directions

fillDeadEnds :: Array (Int, Int) MazeTile -> ST s (Array (Int, Int) MazeTile)
fillDeadEnds a = do
        ma <- MutableArray.thaw a
        let deadEnds = findDeadEnds a
        mapM_ (fillDeadEnd ma) deadEnds
        MutableArray.freeze ma

fillDeadEnd :: STArray s (Int, Int) MazeTile -> Position -> ST s ()
fillDeadEnd a p = do
        MutableArray.writeArray a p Wall
        p' <- walkableNeighbors a p >>= return . head
        t <- MutableArray.readArray a p'
        n <- walkableNeighbors a p' >>= return . List.length
        if n == 1 && t == Unknown then fillDeadEnd a p' else return ()

thawArray :: Array (Int, Int) MazeTile -> ST s (STArray s (Int, Int) MazeTile)
thawArray a = do
        a' <- MutableArray.thaw a
        return a'

solveMaze a = do
        a' <- fillDeadEnds a
        a'' <- thawArray a'
        let s = Array.assocs
                >>> List.filter ((== Start) . snd)
                >>> Maybe.listToMaybe
                >>> Maybe.maybe (error "Start not in map") fst
                $ a
        let e = Array.assocs
                >>> List.filter ((== End) . snd)
                >>> Maybe.listToMaybe
                >>> Maybe.maybe (error "End not in map") fst
                $ a
        MutableArray.writeArray a'' s $ Explored (Map.singleton East 0)
        MutableArray.writeArray a'' e $ Unknown
        solveMaze' (s, East) a''
        fa <- MutableArray.freeze a''
        t <- MutableArray.readArray a'' e
        case t of
                Wall  -> error "Unreachable code"
                Start -> error "Unreachable code"
                End   -> error "Unreachable code"
                Unknown -> error "End was not explored yet"
                Explored m -> return (List.minimum . List.map snd . Map.toList $ m, countTiles fa s e)

countTiles a s p = Set.size . countTiles' a s p $ South

countTiles' :: Array (Int, Int) MazeTile -> Position -> Position -> Direction -> Set Position
countTiles' a s p d
        | p == s    = Set.singleton p
        | otherwise = Set.unions 
                . List.map (Set.insert p) 
                . List.map (uncurry (countTiles' a s)) 
                $ (zip minCostNeighbors minCostDirections)
        where
                minCostNeighbors   = List.map ((p .-.) . directionVector) minCostDirections
                minCostDirections  = List.map fst . List.filter ((== minCost) . snd) . Map.toList $ visits
                visits = case a Array.! p of
                        Explored m -> Map.adjust (+ (-1000)) d m
                minCost = List.minimum . List.map snd . Map.toList $ visits

maybeExplore c p d a = do
        t <- MutableArray.readArray a p
        case t of
                Wall     -> return ()
                Start    -> error "Unreachable code"
                End      -> error "Unreachable code"
                Unknown  -> do
                        MutableArray.writeArray a p $ Explored (Map.singleton d c)
                        solveMaze' (p, d) a
                Explored m -> do
                        let c' = Maybe.maybe c id (m Map.!? d)
                        if c <= c' then do
                                let m' = Map.insert d c m
                                MutableArray.writeArray a p (Explored m')
                                solveMaze' (p, d) a
                        else
                                return ()

solveMaze' :: (Position, Direction) -> STArray s (Int, Int) MazeTile -> ST s ()
solveMaze' s@(p, d) a = do
        t <- MutableArray.readArray a p
        case t of
                Wall -> return ()
                Start -> error "Unreachable code"
                End -> error "Unreachable code"
                Unknown -> error "Starting on unexplored field"
                Explored m -> do
                        let c = m Map.! d
                        maybeExplore (c+1)    (p .+. directionVector d)  d a
                        let d' = turnRight d
                        maybeExplore (c+1001) (p .+. directionVector d') d' a
                        let d'' = turnRight d'
                        maybeExplore (c+1001) (p .+. directionVector d'') d'' a
                        let d''' = turnRight d''
                        maybeExplore (c+1001) (p .+. directionVector d''') d''' a

part1 a = runST (solveMaze a)

main = getContents
        >>= print
        . part1
        . parse

[โ€“] VegOwOtenks 1 points 2 weeks ago

Haskell

I'm late today, anyway here is my blazingly fast solution using haskell

Large codeblock

{-# LANGUAGE MultiWayIf #-}

import Control.Arrow
import Data.Bifunctor hiding (first, second)

import Data.Array.Unboxed (UArray)
import Data.Array.ST (STUArray)
import Control.Monad.ST (ST, runST)
import Control.Monad (join)

import qualified Data.Char as Char
import qualified Data.List as List
import qualified Data.Set as Set
import qualified Data.Array.Unboxed as UArray
import qualified Data.Array.ST as MArray

parse :: String -> (UArray (Int, Int) Char, String)
parse s = (grid, orders)
        where 
                l = lines s
                orderLines = drop 1 . dropWhile (/= "") $ l
                orders     = foldl (++) "" $ orderLines
                gridLines  = takeWhile (/= "") $ l
                gridHeight = length gridLines
                gridWidth  = length . head $ gridLines
                grid       = UArray.listArray ((1, 1), (gridHeight, gridWidth)) . foldl (++) "" $ gridLines

moveRobot :: UArray (Int, Int) Char -> String -> ST s (UArray (Int, Int) Char)
moveRobot g s = do
        let robotPosition = maybe (error "Robot not in grid") fst . List.find ((== '@') . snd) . UArray.assocs $ g
        mg <- MArray.thaw g
        moveRobot' mg robotPosition s

type RobotPosition = (Int, Int)

walkDirection :: (Int, Int) -> (Int, Int) -> [(Int, Int)]
walkDirection p d = iterate (.+. d) p

orderDirection :: Char -> (Int, Int)
orderDirection '>' = ( 0,  1)
orderDirection '<' = ( 0, -1)
orderDirection '^' = (-1,  0)
orderDirection 'v' = ( 1,  0)

(y1, x1) .+. (y2, x2) = (y1 + y2, x1 + x2)

(y1, x1) .*. (y2, x2) = (y1 * y2, x1 * x2)

countBarrels :: STUArray s (Int, Int) Char -> RobotPosition -> (Int, Int) -> ST s Int
countBarrels g p d = do
        currentTile <- MArray.readArray g p
        if currentTile == 'O' then
                do
                        n <- countBarrels g (p .+. d) d
                        return $! n + 1
        else
                return 0

moveRobot' :: STUArray s (Int, Int) Char -> RobotPosition -> String -> ST s (UArray (Int, Int) Char)
moveRobot' g _ [] = MArray.freeze g
moveRobot' g p (o:os) = do
        let direction = orderDirection o
        let nextCoordinate = p .+. direction
        nextTile <- MArray.readArray g nextCoordinate
        case nextTile of
                '#' -> moveRobot' g p os
                '.' -> MArray.writeArray g p '.' 
                        *> MArray.writeArray g nextCoordinate '@'
                        *> moveRobot' g nextCoordinate os
                'O' -> do
                        barrelCount <- countBarrels g nextCoordinate direction
                        let postBarrelPosition = p .+. (direction .*. (1 + barrelCount, 1 + barrelCount))
                        postBarrelTile <- MArray.readArray g postBarrelPosition
                        case postBarrelTile of
                                '#' -> moveRobot' g p os
                                '.' -> MArray.writeArray g p '.'
                                        *> MArray.writeArray g nextCoordinate '@'
                                        *> MArray.writeArray g postBarrelPosition 'O'
                                        *> moveRobot' g nextCoordinate os

part1 (g, o) = UArray.assocs
        >>> filter (snd >>> (== 'O'))
        >>> map (uncurry (+) . ((*100) *** id) . (join bimap pred) . fst)
        >>> sum
        $ g'
        where 
                g' = runST $ (moveRobot g o)

translate :: Char -> String
translate '#'  = "##"
translate '.'  = ".."
translate '@'  = "@."
translate 'O'  = "[]"
translate '\n' = "\n"
translate c    = [c]

moveWideRobot :: UArray (Int, Int) Char -> String -> ST s (UArray (Int, Int) Char)
moveWideRobot g s = do
        let robotPosition = maybe (error "Robot not in grid") fst . List.find ((== '@') . snd) . UArray.assocs $ g
        mg <- MArray.thaw g
        moveWideRobot' mg robotPosition s

moveChestHorizontally g p d = do
        tile <- MArray.readArray g p
        case tile of
                '.' -> return True
                '#' -> return False
                _   -> do
                        let p' = p .+. d
                        canMove <- moveChestHorizontally g p' d
                        if canMove then MArray.writeArray g p' tile else return ()
                        return canMove

boxCounterpart ('[', (y, x)) = (']', (y, x+1))
boxCounterpart (']', (y, x)) = ('[', (y, x-1))

moveChestVertically :: STUArray s (Int, Int) Char -> [(Int, Int)] -> (Int, Int) -> ST s Bool
moveChestVertically g [] d = return True
moveChestVertically g ps d = do
        tiles <- flip zip ps <$> mapM (MArray.readArray g) ps
        let counterParts = List.map boxCounterpart . List.filter (fst >>> flip List.elem "[]") $ tiles
        let tiles' = List.nub $ tiles ++ counterParts
        if | any ((== '#') . fst) tiles' -> return False
           | otherwise -> do
                let boxTiles = List.filter (fst >>> flip List.elem "[]") $ tiles'
                let boxPositions = List.map snd $ boxTiles
                let positionsAhead = List.map (.+. d) $ boxPositions
                success <- moveChestVertically g positionsAhead d
                if success then do
                        mapM_ (second (.+. d) >>> uncurry (flip (MArray.writeArray g))) boxTiles
                        mapM_ (flip (MArray.writeArray g) '.') boxPositions
                else 
                        return ()

                return $ success


moveWideRobot' :: STUArray s (Int, Int) Char -> RobotPosition -> String -> ST s (UArray (Int, Int) Char)
moveWideRobot' g p [] = MArray.freeze g
moveWideRobot' g p (o:os) = do
        let direction = orderDirection o
        let nextCoordinate = p .+. direction
        nextTile <- MArray.readArray g nextCoordinate
        case nextTile of
                '#' -> moveWideRobot' g p os
                '.' -> MArray.writeArray g p '.'
                        *> MArray.writeArray g nextCoordinate '@'
                        *> moveWideRobot' g nextCoordinate os
                '[' -> do
                        success <- if o == '>' 
                        then 
                                moveChestHorizontally g nextCoordinate direction
                        else
                                moveChestVertically g [nextCoordinate, second succ nextCoordinate] direction

                        if success then do
                                MArray.writeArray g p '.'
                                MArray.writeArray g nextCoordinate '@'
                                moveWideRobot' g nextCoordinate os
                        else
                                moveWideRobot' g p os
                ']' -> do
                        success <- if o == '<'
                        then
                                moveChestHorizontally g nextCoordinate direction
                        else
                                moveChestVertically g [nextCoordinate, second pred nextCoordinate] direction

                        if success then do
                                MArray.writeArray g p '.'
                                MArray.writeArray g nextCoordinate '@'
                                moveWideRobot' g nextCoordinate os
                        else
                                moveWideRobot' g p os

part2 (g, o) = UArray.assocs
        >>> List.filter (snd >>> (== '['))
        >>> map (uncurry (+) . ((*100) *** id) . (join bimap pred) . fst)
        >>> sum
        $ g'
        where
                g' = runST $ (moveWideRobot g o)

main = getContents
        >>= print
        . (part1 *** part2)
        . join bimap parse
        . second (List.concatMap translate)
        . join (,)

[โ€“] VegOwOtenks 2 points 2 weeks ago

Haskell

I solved part two interactively, I'm not very happy about it

Reveal Code

import Control.Arrow
import Data.Bifunctor hiding (first, second)
import Control.Monad

import qualified Data.List as List
import qualified Data.Set as Set
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe

parse :: String -> [((Int, Int), (Int, Int))]
parse = map (break (== ' ') >>> second (drop 1) >>> join bimap (drop 2) >>> join bimap (break (== ',')) >>> join bimap (second (drop 1)) >>> join bimap (join bimap read)) . filter (/= "") . lines

moveRobot ((px, py), (vx, vy)) t = (px + t * vx, py + t * vy)

constrainCoordinates (mx, my) (px, py) = (px `mod` mx, py `mod` my)

coordinateConstraints = (101, 103)

robotQuadrant (mx, my) (px, py)
        | px > middleX && py < middleY = Just 1 -- upper right
        | px > middleX && py > middleY = Just 2 -- lower right
        | px < middleX && py > middleY = Just 3 -- lower left
        | px < middleX && py < middleY = Just 4 -- upper left
        | otherwise = Nothing
        where
                middleX = (mx `div` 2)
                middleY = (my `div` 2)

countQuadrants (q1, q2, q3, q4) 1 = (succ q1, q2, q3, q4)
countQuadrants (q1, q2, q3, q4) 2 = (q1, succ q2, q3, q4)
countQuadrants (q1, q2, q3, q4) 3 = (q1, q2, succ q3, q4)
countQuadrants (q1, q2, q3, q4) 4 = (q1, q2, q3, succ q4)

part1 = map (flip moveRobot 100 >>> constrainCoordinates coordinateConstraints)
        >>> map (robotQuadrant coordinateConstraints)
        >>> Maybe.catMaybes
        >>> foldl (countQuadrants) (0, 0, 0, 0)
        >>> \ (a, b, c, d) -> a * b * c * d

showMaybe (Just i) = head . show $ i
showMaybe Nothing  = ' '

buildRobotString robotMap = [ [ showMaybe (robotMap Map.!? (x, y))  | x <- [0..fst coordinateConstraints] ] | y <- [0..snd coordinateConstraints]]

part2 rs t = map (flip moveRobot t >>> constrainCoordinates coordinateConstraints)
        >>> flip zip (repeat 1)
        >>> Map.fromListWith (+)
        >>> buildRobotString
        $ rs

showConstellation (i, s) = do
        putStrLn (replicate 49 '#' ++ show i ++ replicate 49 '#')
        putStrLn $ s

main = do
        f <- getContents
        let i = parse f
        print $ part1 i

        let constellations = map (id &&& (part2 i >>> List.intercalate "\n")) . filter ((== 86) . (`mod` 103)) $ [0..1000000]
        mapM_ showConstellation constellations
        print 7502

[โ€“] VegOwOtenks 1 points 2 weeks ago* (last edited 2 weeks ago) (1 children)

They do, if the remainder returned by divmod(...) wasn't zero then it wouldn't be divisble

[โ€“] VegOwOtenks 4 points 2 weeks ago* (last edited 2 weeks ago)

Haskell

Pen and Paper solved these equations for me.

import Control.Arrow

import qualified Data.Char as Char
import qualified Data.List as List
import qualified Data.Maybe as Maybe


window6 :: [Int] -> [[Int]]
window6 [] = []
window6 is = List.splitAt 6 
        >>> second window6
        >>> uncurry (:)
        $ is

parse :: String -> [[Int]]
parse s = window6 . map read . words . List.filter ((Char.isDigit &&& Char.isSpace) >>> uncurry (||)) $ s

solveEquation (ax:ay:bx:by:tx:ty:[]) transformT
        | (aNum `mod` aDenom) /= 0   = Nothing
        | (bNum `mod` bDenom) /= 0   = Nothing
        | otherwise                  = Just (abs $ aNum `div` aDenom, abs $ bNum `div` bDenom)
        where
                tx' = transformT tx
                ty' = transformT ty
                aNum   = (bx*ty')  - (by*tx')
                aDenom = (ax*by)   - (bx*ay)
                bNum   = (ax*ty')  - (ay*tx')
                bDenom = (ax*by)   - (bx*ay)

part1 = map (flip solveEquation id)
        >>> Maybe.catMaybes
        >>> map (first (*3))
        >>> map (uncurry (+))
        >>> sum
part2 = map (flip solveEquation (+ 10000000000000))
        >>> Maybe.catMaybes
        >>> map (first (*3))
        >>> map (uncurry (+))
        >>> sum

main = getContents
        >>= print
        . (part1 &&& part2)
        . parse

(Edit: coding style)

[โ€“] VegOwOtenks 4 points 3 weeks ago

I had my code run all the time while I coded up the solution for the second part, needless to say, it wouldn't finish.

[โ€“] VegOwOtenks 2 points 3 weeks ago* (last edited 3 weeks ago)

Thank you for showing the floodfill-algorithm using explored/open sets, mine was hellish inefficiently, reminds me of A*.

[โ€“] VegOwOtenks 2 points 3 weeks ago* (last edited 3 weeks ago)

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

[โ€“] VegOwOtenks 2 points 3 weeks ago* (last edited 3 weeks ago) (1 children)

Thank you for the hint, I wouldn't have recognized it because I haven't yet looked into it, I might try it this afternoon if I find the time, I could probably put both the Cache and the current stone count into the monad state?

view more: โ€น prev next โ€บ