import AOC aoc 2015, 6 do import NimbleParsec defparsec( :coord, integer(min: 1) |> ignore(string(",")) |> integer(min: 1) |> label("coordinate pair") ) defparsec( :command, choice([ string("turn on"), string("turn off"), string("toggle") ]) |> ignore(string(" ")) |> parsec(:coord) |> ignore(string(" through ")) |> parsec(:coord) ) def parse_command(line) do command(line) |> then(fn {:ok, v, _, _, _, _} -> v end) |> then(fn ["turn on", x1, y1, x2, y2] -> {:turn_on, {x1, y1}, {x2, y2}} ["turn off", x1, y1, x2, y2] -> {:turn_off, {x1, y1}, {x2, y2}} ["toggle", x1, y1, x2, y2] -> {:toggle, {x1, y1}, {x2, y2}} end) end def command_stream(), do: input_stream() |> Stream.map(&parse_command/1) def p1 do lights = for x <- 0..999, y <- 0..999, into: %{}, do: {{x, y}, 0} command_stream() |> Enum.reduce(lights, fn {:turn_on, from, to}, lights -> set_range(lights, from, to, 1) {:turn_off, from, to}, lights -> set_range(lights, from, to, 0) {:toggle, from, to}, lights -> update_range(lights, from, to, fn 0 -> 1 1 -> 0 end) end) |> Map.values() |> Enum.sum() end def p2 do lights = for x <- 0..999, y <- 0..999, into: %{}, do: {{x, y}, 0} command_stream() |> Enum.reduce(lights, fn {:turn_on, from, to}, lights -> update_range(lights, from, to, &(&1 + 1)) {:turn_off, from, to}, lights -> update_range(lights, from, to, &max(0, &1 - 1)) {:toggle, from, to}, lights -> update_range(lights, from, to, &(&1 + 2)) end) |> Map.values() |> Enum.sum() end def set_range(map, from, to, value), do: update_range(map, from, to, always(value)) def update_range(map, {x1, y1}, {x2, y2}, fun) do for x <- x1..x2, y <- y1..y2, reduce: map do map -> Map.update!(map, {x, y}, fun) end end def always(v), do: fn _ -> v end end