AoC Day 22: Sporifica Virus Part 1, a Solution in Beginner's Haskell

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
2
down vote

favorite












Until recently, I was very much only devoted to imperative languages (mainly C++ and C, to be precise), when I decided to venture into unknown waters by picking up a new, completely different language, which happened to be Haskell, a decision which happened to be influenced by the fact that I owned a copy of "Learn You a Haskell for Great Good!", a book which I very much enjoyed learning from.



Some days ago, I finished said book, and, wanting to apply my newly acquired knowledge, ventured our to find some programming exercises. I quickly remembered Advent of Code, which offers a whole pre-Christmas period's worth of easy to mildly difficult programming problems, a few of which I had already solved during the holidays.



I skimmed through the exercises, looking for one simple enough to be conquerable with my still very inadequate and shaky Haskell skills, and finally chose the task of Day 22.



Problem Description




Diagnostics indicate that the local grid computing cluster has been
contaminated with the Sporifica Virus. The grid computing cluster is a
seemingly-infinite two-dimensional grid of compute nodes. Each node is
either clean or infected by the virus.



To prevent overloading the nodes (which would render them useless to
the virus) or detection by system administrators, exactly one virus
carrier moves through the network, infecting or cleaning nodes as it
moves. The virus carrier is always located on a single node in the
network (the current node) and keeps track of the direction it is
facing.



To avoid detection, the virus carrier works in bursts; in each burst,
it wakes up, does some work, and goes back to sleep. The following
steps are all executed in order one time each burst:



  • If the current node is infected, it turns to its right. Otherwise, it
    turns to its left. (Turning is done in-place; the current node does
    not change.)

  • If the current node is clean, it becomes infected.
    Otherwise, it becomes cleaned. (This is done after the node is
    considered for the purposes of changing direction.)

  • The virus carrier
    moves forward one node in the direction it is facing. Diagnostics have
    also provided a map of the node infection status (your puzzle input).

Clean nodes are shown as .; infected nodes are shown as #. This map
only shows the center of the grid; there are many more nodes beyond
those shown, but none of them are currently infected.



The virus carrier begins in the middle of the map facing up.




(The full puzzle description, including examples, is available on the official AoC website.)



The mentioned puzzle input consists of a file containing a grid of . and #.



My Solution



import Data.List.Index
import qualified Data.Set as Set
import System.Environment
import System.IO
import qualified System.IO.Strict as IOS

type Position = (Int, Int)
type Dimensions = (Int, Int)
type NodeMap = Set.Set Position

data Rotation = Clockwise | Counterclockwise deriving (Eq, Show)
data Direction = North | East | South | West deriving (Eq, Show)


nextDirection :: Rotation -> Direction -> Direction
nextDirection rot dir
| rot == Clockwise && dir == West = North
| rot == Clockwise && dir == East = South
| rot == Clockwise && dir == North = East
| rot == Clockwise && dir == South = West
| rot == Counterclockwise && dir == West = South
| rot == Counterclockwise && dir == East = North
| rot == Counterclockwise && dir == North = West
| rot == Counterclockwise && dir == South = East

parseInput :: String -> (Dimensions, NodeMap)
parseInput s = ((length . head . lines $ s, length . lines $ s),
foldl (set (index, char) ->
if char == '#'
then Set.insert index set
else set)
Set.empty
. ifoldl (ls index line ->
ls ++ zipWith (ix (i, char) ->
((i, ix), char))
(repeat index) (indexed line))

. lines $ s)

simulateNBurstsImpl :: Int -> (Int, Position, Direction, NodeMap)
-> (Int, Position, Direction, NodeMap)
simulateNBurstsImpl 0 x = x
simulateNBurstsImpl i (count, pos, dir, map) = simulateNBurstsImpl
(i - 1) transitionFunction
where
step (a, b) dir
| dir == West = (a - 1, b)
| dir == East = (a + 1, b)
| dir == North = (a, b - 1)
| dir == South = (a, b + 1)
transitionFunction
| Set.member pos map == True = let
nextDir = nextDirection Clockwise dir
in (count, step pos nextDir, nextDir, Set.delete pos map)
| Set.member pos map == False = let
nextDir = nextDirection Counterclockwise dir
in (count + 1, step pos nextDir, nextDir, Set.insert pos map)

simulateNBursts :: Int -> Dimensions -> NodeMap -> Int
simulateNBursts i (width, height) map =
first (simulateNBurstsImpl i (0, startingPos, North, map))
where
startingPos = (width `quot` 2, height `quot` 2)
first (a, _, _, _) = a


main = getArgs >>= (filename : _) ->
withFile filename ReadMode IOS.hGetContents >>= content ->
let parsed = parseInput content in
print . simulateNBursts 10000 (fst parsed) . snd $ parsed


Notes:



  • The program takes a single argument on the commandline; the path to the file containing the starting grid.

  • I decided on using a set as the underlying data structure as it makes it easy to work with growing grids. My first attempt used a two-dimensional sequence, but extending the grid turned out to be too much of a hassle for my taste.

  • The code assumes that all inputs are valid, including the fact that a commandline parameter was passed and points to a valid file.

Review Requests



Please feel free to review anything and everything that comes to mind! That said, I do have a few concrete questions:



  1. Having simulateNBursts as a beautified interface to simulateNBurstsImpl seems kind of ugly to me. Is there a way to clean this up and join the two functions? Or is this a common pattern?

  2. How readable is this code? As its author, I find it hard to judge how (un-)pleasant this code is to the eye of a third person, especially since I have next to no experience in reading and writing Haskell code. What can I do to improve readability?


  3. nextDirection seems very verbose to me. Is there a more concise way to implement it?






share|improve this question

























    up vote
    2
    down vote

    favorite












    Until recently, I was very much only devoted to imperative languages (mainly C++ and C, to be precise), when I decided to venture into unknown waters by picking up a new, completely different language, which happened to be Haskell, a decision which happened to be influenced by the fact that I owned a copy of "Learn You a Haskell for Great Good!", a book which I very much enjoyed learning from.



    Some days ago, I finished said book, and, wanting to apply my newly acquired knowledge, ventured our to find some programming exercises. I quickly remembered Advent of Code, which offers a whole pre-Christmas period's worth of easy to mildly difficult programming problems, a few of which I had already solved during the holidays.



    I skimmed through the exercises, looking for one simple enough to be conquerable with my still very inadequate and shaky Haskell skills, and finally chose the task of Day 22.



    Problem Description




    Diagnostics indicate that the local grid computing cluster has been
    contaminated with the Sporifica Virus. The grid computing cluster is a
    seemingly-infinite two-dimensional grid of compute nodes. Each node is
    either clean or infected by the virus.



    To prevent overloading the nodes (which would render them useless to
    the virus) or detection by system administrators, exactly one virus
    carrier moves through the network, infecting or cleaning nodes as it
    moves. The virus carrier is always located on a single node in the
    network (the current node) and keeps track of the direction it is
    facing.



    To avoid detection, the virus carrier works in bursts; in each burst,
    it wakes up, does some work, and goes back to sleep. The following
    steps are all executed in order one time each burst:



    • If the current node is infected, it turns to its right. Otherwise, it
      turns to its left. (Turning is done in-place; the current node does
      not change.)

    • If the current node is clean, it becomes infected.
      Otherwise, it becomes cleaned. (This is done after the node is
      considered for the purposes of changing direction.)

    • The virus carrier
      moves forward one node in the direction it is facing. Diagnostics have
      also provided a map of the node infection status (your puzzle input).

    Clean nodes are shown as .; infected nodes are shown as #. This map
    only shows the center of the grid; there are many more nodes beyond
    those shown, but none of them are currently infected.



    The virus carrier begins in the middle of the map facing up.




    (The full puzzle description, including examples, is available on the official AoC website.)



    The mentioned puzzle input consists of a file containing a grid of . and #.



    My Solution



    import Data.List.Index
    import qualified Data.Set as Set
    import System.Environment
    import System.IO
    import qualified System.IO.Strict as IOS

    type Position = (Int, Int)
    type Dimensions = (Int, Int)
    type NodeMap = Set.Set Position

    data Rotation = Clockwise | Counterclockwise deriving (Eq, Show)
    data Direction = North | East | South | West deriving (Eq, Show)


    nextDirection :: Rotation -> Direction -> Direction
    nextDirection rot dir
    | rot == Clockwise && dir == West = North
    | rot == Clockwise && dir == East = South
    | rot == Clockwise && dir == North = East
    | rot == Clockwise && dir == South = West
    | rot == Counterclockwise && dir == West = South
    | rot == Counterclockwise && dir == East = North
    | rot == Counterclockwise && dir == North = West
    | rot == Counterclockwise && dir == South = East

    parseInput :: String -> (Dimensions, NodeMap)
    parseInput s = ((length . head . lines $ s, length . lines $ s),
    foldl (set (index, char) ->
    if char == '#'
    then Set.insert index set
    else set)
    Set.empty
    . ifoldl (ls index line ->
    ls ++ zipWith (ix (i, char) ->
    ((i, ix), char))
    (repeat index) (indexed line))

    . lines $ s)

    simulateNBurstsImpl :: Int -> (Int, Position, Direction, NodeMap)
    -> (Int, Position, Direction, NodeMap)
    simulateNBurstsImpl 0 x = x
    simulateNBurstsImpl i (count, pos, dir, map) = simulateNBurstsImpl
    (i - 1) transitionFunction
    where
    step (a, b) dir
    | dir == West = (a - 1, b)
    | dir == East = (a + 1, b)
    | dir == North = (a, b - 1)
    | dir == South = (a, b + 1)
    transitionFunction
    | Set.member pos map == True = let
    nextDir = nextDirection Clockwise dir
    in (count, step pos nextDir, nextDir, Set.delete pos map)
    | Set.member pos map == False = let
    nextDir = nextDirection Counterclockwise dir
    in (count + 1, step pos nextDir, nextDir, Set.insert pos map)

    simulateNBursts :: Int -> Dimensions -> NodeMap -> Int
    simulateNBursts i (width, height) map =
    first (simulateNBurstsImpl i (0, startingPos, North, map))
    where
    startingPos = (width `quot` 2, height `quot` 2)
    first (a, _, _, _) = a


    main = getArgs >>= (filename : _) ->
    withFile filename ReadMode IOS.hGetContents >>= content ->
    let parsed = parseInput content in
    print . simulateNBursts 10000 (fst parsed) . snd $ parsed


    Notes:



    • The program takes a single argument on the commandline; the path to the file containing the starting grid.

    • I decided on using a set as the underlying data structure as it makes it easy to work with growing grids. My first attempt used a two-dimensional sequence, but extending the grid turned out to be too much of a hassle for my taste.

    • The code assumes that all inputs are valid, including the fact that a commandline parameter was passed and points to a valid file.

    Review Requests



    Please feel free to review anything and everything that comes to mind! That said, I do have a few concrete questions:



    1. Having simulateNBursts as a beautified interface to simulateNBurstsImpl seems kind of ugly to me. Is there a way to clean this up and join the two functions? Or is this a common pattern?

    2. How readable is this code? As its author, I find it hard to judge how (un-)pleasant this code is to the eye of a third person, especially since I have next to no experience in reading and writing Haskell code. What can I do to improve readability?


    3. nextDirection seems very verbose to me. Is there a more concise way to implement it?






    share|improve this question





















      up vote
      2
      down vote

      favorite









      up vote
      2
      down vote

      favorite











      Until recently, I was very much only devoted to imperative languages (mainly C++ and C, to be precise), when I decided to venture into unknown waters by picking up a new, completely different language, which happened to be Haskell, a decision which happened to be influenced by the fact that I owned a copy of "Learn You a Haskell for Great Good!", a book which I very much enjoyed learning from.



      Some days ago, I finished said book, and, wanting to apply my newly acquired knowledge, ventured our to find some programming exercises. I quickly remembered Advent of Code, which offers a whole pre-Christmas period's worth of easy to mildly difficult programming problems, a few of which I had already solved during the holidays.



      I skimmed through the exercises, looking for one simple enough to be conquerable with my still very inadequate and shaky Haskell skills, and finally chose the task of Day 22.



      Problem Description




      Diagnostics indicate that the local grid computing cluster has been
      contaminated with the Sporifica Virus. The grid computing cluster is a
      seemingly-infinite two-dimensional grid of compute nodes. Each node is
      either clean or infected by the virus.



      To prevent overloading the nodes (which would render them useless to
      the virus) or detection by system administrators, exactly one virus
      carrier moves through the network, infecting or cleaning nodes as it
      moves. The virus carrier is always located on a single node in the
      network (the current node) and keeps track of the direction it is
      facing.



      To avoid detection, the virus carrier works in bursts; in each burst,
      it wakes up, does some work, and goes back to sleep. The following
      steps are all executed in order one time each burst:



      • If the current node is infected, it turns to its right. Otherwise, it
        turns to its left. (Turning is done in-place; the current node does
        not change.)

      • If the current node is clean, it becomes infected.
        Otherwise, it becomes cleaned. (This is done after the node is
        considered for the purposes of changing direction.)

      • The virus carrier
        moves forward one node in the direction it is facing. Diagnostics have
        also provided a map of the node infection status (your puzzle input).

      Clean nodes are shown as .; infected nodes are shown as #. This map
      only shows the center of the grid; there are many more nodes beyond
      those shown, but none of them are currently infected.



      The virus carrier begins in the middle of the map facing up.




      (The full puzzle description, including examples, is available on the official AoC website.)



      The mentioned puzzle input consists of a file containing a grid of . and #.



      My Solution



      import Data.List.Index
      import qualified Data.Set as Set
      import System.Environment
      import System.IO
      import qualified System.IO.Strict as IOS

      type Position = (Int, Int)
      type Dimensions = (Int, Int)
      type NodeMap = Set.Set Position

      data Rotation = Clockwise | Counterclockwise deriving (Eq, Show)
      data Direction = North | East | South | West deriving (Eq, Show)


      nextDirection :: Rotation -> Direction -> Direction
      nextDirection rot dir
      | rot == Clockwise && dir == West = North
      | rot == Clockwise && dir == East = South
      | rot == Clockwise && dir == North = East
      | rot == Clockwise && dir == South = West
      | rot == Counterclockwise && dir == West = South
      | rot == Counterclockwise && dir == East = North
      | rot == Counterclockwise && dir == North = West
      | rot == Counterclockwise && dir == South = East

      parseInput :: String -> (Dimensions, NodeMap)
      parseInput s = ((length . head . lines $ s, length . lines $ s),
      foldl (set (index, char) ->
      if char == '#'
      then Set.insert index set
      else set)
      Set.empty
      . ifoldl (ls index line ->
      ls ++ zipWith (ix (i, char) ->
      ((i, ix), char))
      (repeat index) (indexed line))

      . lines $ s)

      simulateNBurstsImpl :: Int -> (Int, Position, Direction, NodeMap)
      -> (Int, Position, Direction, NodeMap)
      simulateNBurstsImpl 0 x = x
      simulateNBurstsImpl i (count, pos, dir, map) = simulateNBurstsImpl
      (i - 1) transitionFunction
      where
      step (a, b) dir
      | dir == West = (a - 1, b)
      | dir == East = (a + 1, b)
      | dir == North = (a, b - 1)
      | dir == South = (a, b + 1)
      transitionFunction
      | Set.member pos map == True = let
      nextDir = nextDirection Clockwise dir
      in (count, step pos nextDir, nextDir, Set.delete pos map)
      | Set.member pos map == False = let
      nextDir = nextDirection Counterclockwise dir
      in (count + 1, step pos nextDir, nextDir, Set.insert pos map)

      simulateNBursts :: Int -> Dimensions -> NodeMap -> Int
      simulateNBursts i (width, height) map =
      first (simulateNBurstsImpl i (0, startingPos, North, map))
      where
      startingPos = (width `quot` 2, height `quot` 2)
      first (a, _, _, _) = a


      main = getArgs >>= (filename : _) ->
      withFile filename ReadMode IOS.hGetContents >>= content ->
      let parsed = parseInput content in
      print . simulateNBursts 10000 (fst parsed) . snd $ parsed


      Notes:



      • The program takes a single argument on the commandline; the path to the file containing the starting grid.

      • I decided on using a set as the underlying data structure as it makes it easy to work with growing grids. My first attempt used a two-dimensional sequence, but extending the grid turned out to be too much of a hassle for my taste.

      • The code assumes that all inputs are valid, including the fact that a commandline parameter was passed and points to a valid file.

      Review Requests



      Please feel free to review anything and everything that comes to mind! That said, I do have a few concrete questions:



      1. Having simulateNBursts as a beautified interface to simulateNBurstsImpl seems kind of ugly to me. Is there a way to clean this up and join the two functions? Or is this a common pattern?

      2. How readable is this code? As its author, I find it hard to judge how (un-)pleasant this code is to the eye of a third person, especially since I have next to no experience in reading and writing Haskell code. What can I do to improve readability?


      3. nextDirection seems very verbose to me. Is there a more concise way to implement it?






      share|improve this question











      Until recently, I was very much only devoted to imperative languages (mainly C++ and C, to be precise), when I decided to venture into unknown waters by picking up a new, completely different language, which happened to be Haskell, a decision which happened to be influenced by the fact that I owned a copy of "Learn You a Haskell for Great Good!", a book which I very much enjoyed learning from.



      Some days ago, I finished said book, and, wanting to apply my newly acquired knowledge, ventured our to find some programming exercises. I quickly remembered Advent of Code, which offers a whole pre-Christmas period's worth of easy to mildly difficult programming problems, a few of which I had already solved during the holidays.



      I skimmed through the exercises, looking for one simple enough to be conquerable with my still very inadequate and shaky Haskell skills, and finally chose the task of Day 22.



      Problem Description




      Diagnostics indicate that the local grid computing cluster has been
      contaminated with the Sporifica Virus. The grid computing cluster is a
      seemingly-infinite two-dimensional grid of compute nodes. Each node is
      either clean or infected by the virus.



      To prevent overloading the nodes (which would render them useless to
      the virus) or detection by system administrators, exactly one virus
      carrier moves through the network, infecting or cleaning nodes as it
      moves. The virus carrier is always located on a single node in the
      network (the current node) and keeps track of the direction it is
      facing.



      To avoid detection, the virus carrier works in bursts; in each burst,
      it wakes up, does some work, and goes back to sleep. The following
      steps are all executed in order one time each burst:



      • If the current node is infected, it turns to its right. Otherwise, it
        turns to its left. (Turning is done in-place; the current node does
        not change.)

      • If the current node is clean, it becomes infected.
        Otherwise, it becomes cleaned. (This is done after the node is
        considered for the purposes of changing direction.)

      • The virus carrier
        moves forward one node in the direction it is facing. Diagnostics have
        also provided a map of the node infection status (your puzzle input).

      Clean nodes are shown as .; infected nodes are shown as #. This map
      only shows the center of the grid; there are many more nodes beyond
      those shown, but none of them are currently infected.



      The virus carrier begins in the middle of the map facing up.




      (The full puzzle description, including examples, is available on the official AoC website.)



      The mentioned puzzle input consists of a file containing a grid of . and #.



      My Solution



      import Data.List.Index
      import qualified Data.Set as Set
      import System.Environment
      import System.IO
      import qualified System.IO.Strict as IOS

      type Position = (Int, Int)
      type Dimensions = (Int, Int)
      type NodeMap = Set.Set Position

      data Rotation = Clockwise | Counterclockwise deriving (Eq, Show)
      data Direction = North | East | South | West deriving (Eq, Show)


      nextDirection :: Rotation -> Direction -> Direction
      nextDirection rot dir
      | rot == Clockwise && dir == West = North
      | rot == Clockwise && dir == East = South
      | rot == Clockwise && dir == North = East
      | rot == Clockwise && dir == South = West
      | rot == Counterclockwise && dir == West = South
      | rot == Counterclockwise && dir == East = North
      | rot == Counterclockwise && dir == North = West
      | rot == Counterclockwise && dir == South = East

      parseInput :: String -> (Dimensions, NodeMap)
      parseInput s = ((length . head . lines $ s, length . lines $ s),
      foldl (set (index, char) ->
      if char == '#'
      then Set.insert index set
      else set)
      Set.empty
      . ifoldl (ls index line ->
      ls ++ zipWith (ix (i, char) ->
      ((i, ix), char))
      (repeat index) (indexed line))

      . lines $ s)

      simulateNBurstsImpl :: Int -> (Int, Position, Direction, NodeMap)
      -> (Int, Position, Direction, NodeMap)
      simulateNBurstsImpl 0 x = x
      simulateNBurstsImpl i (count, pos, dir, map) = simulateNBurstsImpl
      (i - 1) transitionFunction
      where
      step (a, b) dir
      | dir == West = (a - 1, b)
      | dir == East = (a + 1, b)
      | dir == North = (a, b - 1)
      | dir == South = (a, b + 1)
      transitionFunction
      | Set.member pos map == True = let
      nextDir = nextDirection Clockwise dir
      in (count, step pos nextDir, nextDir, Set.delete pos map)
      | Set.member pos map == False = let
      nextDir = nextDirection Counterclockwise dir
      in (count + 1, step pos nextDir, nextDir, Set.insert pos map)

      simulateNBursts :: Int -> Dimensions -> NodeMap -> Int
      simulateNBursts i (width, height) map =
      first (simulateNBurstsImpl i (0, startingPos, North, map))
      where
      startingPos = (width `quot` 2, height `quot` 2)
      first (a, _, _, _) = a


      main = getArgs >>= (filename : _) ->
      withFile filename ReadMode IOS.hGetContents >>= content ->
      let parsed = parseInput content in
      print . simulateNBursts 10000 (fst parsed) . snd $ parsed


      Notes:



      • The program takes a single argument on the commandline; the path to the file containing the starting grid.

      • I decided on using a set as the underlying data structure as it makes it easy to work with growing grids. My first attempt used a two-dimensional sequence, but extending the grid turned out to be too much of a hassle for my taste.

      • The code assumes that all inputs are valid, including the fact that a commandline parameter was passed and points to a valid file.

      Review Requests



      Please feel free to review anything and everything that comes to mind! That said, I do have a few concrete questions:



      1. Having simulateNBursts as a beautified interface to simulateNBurstsImpl seems kind of ugly to me. Is there a way to clean this up and join the two functions? Or is this a common pattern?

      2. How readable is this code? As its author, I find it hard to judge how (un-)pleasant this code is to the eye of a third person, especially since I have next to no experience in reading and writing Haskell code. What can I do to improve readability?


      3. nextDirection seems very verbose to me. Is there a more concise way to implement it?








      share|improve this question










      share|improve this question




      share|improve this question









      asked Feb 10 at 22:35









      Ben Steffan

      4,85011234




      4,85011234




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          1
          down vote



          accepted










          You can inline simulateNBurstsImpl if you get rid of the recursion:



          simulateNBurstsImpl n = foldr (.) id $ replicate n transitionFunction


          Combining zipWith with repeat is a fool's errand. (I don't know where you get ifoldl and indexed, so I'll assume they start at 0.)



          parseInput :: String -> (Dimensions, NodeMap)
          parseInput s = ((length . head $ lines s, length $ lines s), Set.fromList
          [ (x, y)
          | (y, line) <- zip [0..] $ lines s
          , (x, char) <- zip [0..] line
          , char == '#'
          ])


          Direction and Rotation can be directly represented as offsets and functions on offsets.



          import Data.NumInstances.Tuple

          type Rotation = Direction -> Direction
          type Direction = (Int, Int)

          transitionFunction (count, pos, (dx,dy), map) = if Set.member pos map
          then let nextdir = (-dy,dx)
          in (count , pos + nextDir, nextDir, Set.delete pos map)
          else let nextdir = (dy,-dx)
          in (count + 1, pos + nextDir, nextDir, Set.insert pos map)


          Optionally: lens and State specialize in this fiddly sort of stuff.



          data S = S
          _count :: Int
          , _pos :: Position
          , _dir :: Int
          , _map :: NodeMap


          makeLenses ''S

          simulateNBursts i (width, height) map =
          (`evalState` (0, (width `quot` 2, height `quot` 2), (0,-1), map)) $ do
          replicateM_ i $ do
          hashtagged <- map . contains pos <<%= negate
          nextDir <- dir <%= (x,y) -> if hashtagged then (-y,x) else (y,-x)
          pos += nextDir
          unless hashtagged $ count += 1
          use count





          share|improve this answer























          • Thank you for the answer. ifoldl and indexed are from the ilist package (from Data.List.Index and start, as you guessed, from 0.
            – Ben Steffan
            Feb 11 at 16:23










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f187283%2faoc-day-22-sporifica-virus-part-1-a-solution-in-beginners-haskell%23new-answer', 'question_page');

          );

          Post as a guest






























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          1
          down vote



          accepted










          You can inline simulateNBurstsImpl if you get rid of the recursion:



          simulateNBurstsImpl n = foldr (.) id $ replicate n transitionFunction


          Combining zipWith with repeat is a fool's errand. (I don't know where you get ifoldl and indexed, so I'll assume they start at 0.)



          parseInput :: String -> (Dimensions, NodeMap)
          parseInput s = ((length . head $ lines s, length $ lines s), Set.fromList
          [ (x, y)
          | (y, line) <- zip [0..] $ lines s
          , (x, char) <- zip [0..] line
          , char == '#'
          ])


          Direction and Rotation can be directly represented as offsets and functions on offsets.



          import Data.NumInstances.Tuple

          type Rotation = Direction -> Direction
          type Direction = (Int, Int)

          transitionFunction (count, pos, (dx,dy), map) = if Set.member pos map
          then let nextdir = (-dy,dx)
          in (count , pos + nextDir, nextDir, Set.delete pos map)
          else let nextdir = (dy,-dx)
          in (count + 1, pos + nextDir, nextDir, Set.insert pos map)


          Optionally: lens and State specialize in this fiddly sort of stuff.



          data S = S
          _count :: Int
          , _pos :: Position
          , _dir :: Int
          , _map :: NodeMap


          makeLenses ''S

          simulateNBursts i (width, height) map =
          (`evalState` (0, (width `quot` 2, height `quot` 2), (0,-1), map)) $ do
          replicateM_ i $ do
          hashtagged <- map . contains pos <<%= negate
          nextDir <- dir <%= (x,y) -> if hashtagged then (-y,x) else (y,-x)
          pos += nextDir
          unless hashtagged $ count += 1
          use count





          share|improve this answer























          • Thank you for the answer. ifoldl and indexed are from the ilist package (from Data.List.Index and start, as you guessed, from 0.
            – Ben Steffan
            Feb 11 at 16:23














          up vote
          1
          down vote



          accepted










          You can inline simulateNBurstsImpl if you get rid of the recursion:



          simulateNBurstsImpl n = foldr (.) id $ replicate n transitionFunction


          Combining zipWith with repeat is a fool's errand. (I don't know where you get ifoldl and indexed, so I'll assume they start at 0.)



          parseInput :: String -> (Dimensions, NodeMap)
          parseInput s = ((length . head $ lines s, length $ lines s), Set.fromList
          [ (x, y)
          | (y, line) <- zip [0..] $ lines s
          , (x, char) <- zip [0..] line
          , char == '#'
          ])


          Direction and Rotation can be directly represented as offsets and functions on offsets.



          import Data.NumInstances.Tuple

          type Rotation = Direction -> Direction
          type Direction = (Int, Int)

          transitionFunction (count, pos, (dx,dy), map) = if Set.member pos map
          then let nextdir = (-dy,dx)
          in (count , pos + nextDir, nextDir, Set.delete pos map)
          else let nextdir = (dy,-dx)
          in (count + 1, pos + nextDir, nextDir, Set.insert pos map)


          Optionally: lens and State specialize in this fiddly sort of stuff.



          data S = S
          _count :: Int
          , _pos :: Position
          , _dir :: Int
          , _map :: NodeMap


          makeLenses ''S

          simulateNBursts i (width, height) map =
          (`evalState` (0, (width `quot` 2, height `quot` 2), (0,-1), map)) $ do
          replicateM_ i $ do
          hashtagged <- map . contains pos <<%= negate
          nextDir <- dir <%= (x,y) -> if hashtagged then (-y,x) else (y,-x)
          pos += nextDir
          unless hashtagged $ count += 1
          use count





          share|improve this answer























          • Thank you for the answer. ifoldl and indexed are from the ilist package (from Data.List.Index and start, as you guessed, from 0.
            – Ben Steffan
            Feb 11 at 16:23












          up vote
          1
          down vote



          accepted







          up vote
          1
          down vote



          accepted






          You can inline simulateNBurstsImpl if you get rid of the recursion:



          simulateNBurstsImpl n = foldr (.) id $ replicate n transitionFunction


          Combining zipWith with repeat is a fool's errand. (I don't know where you get ifoldl and indexed, so I'll assume they start at 0.)



          parseInput :: String -> (Dimensions, NodeMap)
          parseInput s = ((length . head $ lines s, length $ lines s), Set.fromList
          [ (x, y)
          | (y, line) <- zip [0..] $ lines s
          , (x, char) <- zip [0..] line
          , char == '#'
          ])


          Direction and Rotation can be directly represented as offsets and functions on offsets.



          import Data.NumInstances.Tuple

          type Rotation = Direction -> Direction
          type Direction = (Int, Int)

          transitionFunction (count, pos, (dx,dy), map) = if Set.member pos map
          then let nextdir = (-dy,dx)
          in (count , pos + nextDir, nextDir, Set.delete pos map)
          else let nextdir = (dy,-dx)
          in (count + 1, pos + nextDir, nextDir, Set.insert pos map)


          Optionally: lens and State specialize in this fiddly sort of stuff.



          data S = S
          _count :: Int
          , _pos :: Position
          , _dir :: Int
          , _map :: NodeMap


          makeLenses ''S

          simulateNBursts i (width, height) map =
          (`evalState` (0, (width `quot` 2, height `quot` 2), (0,-1), map)) $ do
          replicateM_ i $ do
          hashtagged <- map . contains pos <<%= negate
          nextDir <- dir <%= (x,y) -> if hashtagged then (-y,x) else (y,-x)
          pos += nextDir
          unless hashtagged $ count += 1
          use count





          share|improve this answer















          You can inline simulateNBurstsImpl if you get rid of the recursion:



          simulateNBurstsImpl n = foldr (.) id $ replicate n transitionFunction


          Combining zipWith with repeat is a fool's errand. (I don't know where you get ifoldl and indexed, so I'll assume they start at 0.)



          parseInput :: String -> (Dimensions, NodeMap)
          parseInput s = ((length . head $ lines s, length $ lines s), Set.fromList
          [ (x, y)
          | (y, line) <- zip [0..] $ lines s
          , (x, char) <- zip [0..] line
          , char == '#'
          ])


          Direction and Rotation can be directly represented as offsets and functions on offsets.



          import Data.NumInstances.Tuple

          type Rotation = Direction -> Direction
          type Direction = (Int, Int)

          transitionFunction (count, pos, (dx,dy), map) = if Set.member pos map
          then let nextdir = (-dy,dx)
          in (count , pos + nextDir, nextDir, Set.delete pos map)
          else let nextdir = (dy,-dx)
          in (count + 1, pos + nextDir, nextDir, Set.insert pos map)


          Optionally: lens and State specialize in this fiddly sort of stuff.



          data S = S
          _count :: Int
          , _pos :: Position
          , _dir :: Int
          , _map :: NodeMap


          makeLenses ''S

          simulateNBursts i (width, height) map =
          (`evalState` (0, (width `quot` 2, height `quot` 2), (0,-1), map)) $ do
          replicateM_ i $ do
          hashtagged <- map . contains pos <<%= negate
          nextDir <- dir <%= (x,y) -> if hashtagged then (-y,x) else (y,-x)
          pos += nextDir
          unless hashtagged $ count += 1
          use count






          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited Feb 12 at 18:20


























          answered Feb 11 at 16:16









          Gurkenglas

          2,658411




          2,658411











          • Thank you for the answer. ifoldl and indexed are from the ilist package (from Data.List.Index and start, as you guessed, from 0.
            – Ben Steffan
            Feb 11 at 16:23
















          • Thank you for the answer. ifoldl and indexed are from the ilist package (from Data.List.Index and start, as you guessed, from 0.
            – Ben Steffan
            Feb 11 at 16:23















          Thank you for the answer. ifoldl and indexed are from the ilist package (from Data.List.Index and start, as you guessed, from 0.
          – Ben Steffan
          Feb 11 at 16:23




          Thank you for the answer. ifoldl and indexed are from the ilist package (from Data.List.Index and start, as you guessed, from 0.
          – Ben Steffan
          Feb 11 at 16:23












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f187283%2faoc-day-22-sporifica-virus-part-1-a-solution-in-beginners-haskell%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Python Lists

          Aion

          JavaScript Array Iteration Methods