cblavier

cblavier

Hey there :wave:

CleanShot 2020-12-11 at 10.59.25@2x

No magic or algorithmic finesse today, I just finished the challenge and I my code is quite slow (1sec for part1, 3sec for part2).

But at least I managed to made my code readable and to reuse most of part1 for part2.

My code: part1 / part2

Showing Posts 1 to 10

Aetherus

Aetherus

I did this too, and I want to see better solutions. :slight_smile:

LostKobrakai

LostKobrakai

I’ve gone ahead with a genserver for the simulation (not really needed but :man_shrugging:) and a state struct, which even implements String.Chars (mostly for comparing to the examples in tests). I like the code, but it’s not very fast as well.

https://github.com/LostKobrakai/aoc2020/commit/52a9f4866e22ccd6609dd6acc875fa9d1323f8a7

cblavier

cblavier OP

by the way, I often need to do some while true ... break in AOC challenges
Here is what I use instead:

Stream.cycle([0])
|> Enum.reduce_while([], fn _, acc ->
   ...
end)

Working, but I don’t really like the [0] list coming from nowhere.
Any other way?

LostKobrakai

LostKobrakai

aaronnamba

aaronnamba

There are many cases where it works well to build a Stream, filter it, and take 1.

mexicat

mexicat

Mine is also quite slow (~3s for p2) but at least the code is not too convoluted this time :slight_smile:

defmodule AdventOfCode.Day11 do
  def part1(input) do
    input
    |> make_grid()
    |> next(&switch_status/2)
  end

  def part2(input) do
    input
    |> make_grid()
    |> next(&switch_status_2/2)
  end

  def make_grid(input) do
    for {row, row_index} <- input |> String.split("\n") |> Enum.with_index(),
        {col, col_index} <- row |> String.codepoints() |> Enum.with_index(),
        into: %{},
        do: {{col_index, row_index}, col}
  end

  def seat_status(grid, x, y) do
    Map.get(grid, {x, y})
  end

  def count_occupied_seats(grid) do
    grid
    |> Map.values()
    |> Enum.count(&(&1 == "#"))
  end

  def next(grid, switch_fn) do
    new_grid =
      grid
      |> Enum.filter(fn {_k, v} -> v != "." end)
      |> Enum.map(&switch_fn.(grid, &1))
      |> Enum.into(grid)

    if new_grid == grid, do: count_occupied_seats(grid), else: next(new_grid, switch_fn)
  end

  def switch_status(grid, {{x, y}, status}) do
    adjacent =
      for other_x <- (x - 1)..(x + 1),
          other_y <- (y - 1)..(y + 1),
          {other_x, other_y} != {x, y},
          seat_status(grid, other_x, other_y) == "#" do
        true
      end
      |> length()

    cond do
      status == "#" and adjacent >= 4 -> {{x, y}, "L"}
      status == "L" and adjacent == 0 -> {{x, y}, "#"}
      true -> {{x, y}, status}
    end
  end

  def switch_status_2(grid, {pos, status}) do
    in_sight =
      [{-1, -1}, {-1, 0}, {-1, +1}, {0, -1}, {0, +1}, {1, -1}, {1, 0}, {1, 1}]
      |> Enum.map(&in_line_of_sight(grid, pos, &1))
      |> Enum.count(& &1)

    cond do
      status == "#" and in_sight >= 5 -> {pos, "L"}
      status == "L" and in_sight == 0 -> {pos, "#"}
      true -> {pos, status}
    end
  end

  def in_line_of_sight(grid, {x, y}, dir = {dir_x, dir_y}) do
    {new_x, new_y} = {x + dir_x, y + dir_y}

    case seat_status(grid, new_x, new_y) do
      "#" -> true
      "L" -> false
      nil -> false
      _ -> in_line_of_sight(grid, {new_x, new_y}, dir)
    end
  end

  # for debugging
  def visualize_grid(grid) do
    max_x = grid |> Enum.map(fn {{x, _y}, _} -> x end) |> Enum.max()
    max_y = grid |> Enum.map(fn {{_x, y}, _} -> y end) |> Enum.max()

    for y <- 0..max_y do
      for x <- 0..max_x do
        Map.get(grid, {x, y})
      end
      |> Enum.join()
    end
    |> Enum.join("\n")
    |> IO.puts()
  end
end
faried

faried

Could’ve used a better line of sight calculation. Oh, well.

defmodule Day11.Grid do
  @empty "L"
  @occupied "#"
  @space "."
  @seats [@empty, @occupied]

  def seat?(row, col, grid), do: Map.get(grid, {row, col}) in @seats

  def up(-1, _col, _area), do: {-1, -1}

  def up(row, col, {_, _, grid} = area) do
    if seat?(row, col, grid), do: {row, col}, else: up(row - 1, col, area)
  end

  def down(row, _, {numrows, _, _}) when row == numrows, do: {-1, -1}

  def down(row, col, {_, _, grid} = area) do
    if seat?(row, col, grid), do: {row, col}, else: down(row + 1, col, area)
  end

  def left(_row, -1, _area), do: {-1, -1}

  def left(row, col, {_, _, grid} = area) do
    if seat?(row, col, grid), do: {row, col}, else: left(row, col - 1, area)
  end

  def right(_, col, {_, numcols, _}) when col == numcols, do: {-1, -1}

  def right(row, col, {_, _, grid} = area) do
    if seat?(row, col, grid), do: {row, col}, else: right(row, col + 1, area)
  end

  def ne(-1, _col, _area), do: {-1, -1}
  def ne(_row, -1, _area), do: {-1, -1}

  def ne(row, col, {_, _, grid} = area) do
    if seat?(row, col, grid), do: {row, col}, else: ne(row - 1, col - 1, area)
  end

  def nw(-1, _col, _area), do: {-1, -1}
  def nw(_row, col, {_, numcols, _}) when col == numcols, do: {-1, -1}

  def nw(row, col, {_, _, grid} = area) do
    if seat?(row, col, grid), do: {row, col}, else: nw(row - 1, col + 1, area)
  end

  def se(_row, -1, _area), do: {-1, -1}
  def se(row, _col, {numrows, _, _}) when row == numrows, do: {-1, -1}

  def se(row, col, {_, _, grid} = area) do
    if seat?(row, col, grid), do: {row, col}, else: se(row + 1, col - 1, area)
  end

  def sw(row, col, {numrows, numcols, _}) when row == numrows or col == numcols,
    do: {-1, -1}

  def sw(row, col, {_, _, grid} = area) do
    if seat?(row, col, grid), do: {row, col}, else: sw(row + 1, col + 1, area)
  end

  def dirs(row, col, _area, 1 = _part) do
    [
      {row, col - 1},
      {row, col + 1},
      {row - 1, col},
      {row + 1, col},
      {row - 1, col - 1},
      {row - 1, col + 1},
      {row + 1, col - 1},
      {row + 1, col + 1}
    ]
  end

  def dirs(row, col, area, 2 = _part) do
    [
      left(row, col - 1, area),
      right(row, col + 1, area),
      up(row - 1, col, area),
      down(row + 1, col, area),
      ne(row - 1, col - 1, area),
      nw(row - 1, col + 1, area),
      se(row + 1, col - 1, area),
      sw(row + 1, col + 1, area)
    ]
  end

  def occupiedaround(row, col, {numrows, numcols, grid} = area, part) do
    dirs(row, col, area, part)
    |> Enum.count(fn {r, c} ->
      r in 0..(numrows - 1) and c in 0..(numcols - 1) and Map.get(grid, {r, c}) == @occupied
    end)
  end

  def change({r, c, @space}, _area, _part), do: {{r, c}, @space}

  def change({r, c, @empty}, area, part) do
    countoccupied = occupiedaround(r, c, area, part)
    {{r, c}, if(countoccupied == 0, do: @occupied, else: @empty)}
  end

  def change({r, c, @occupied}, area, part) do
    countoccupied = occupiedaround(r, c, area, part)

    maxfree = if part == 1, do: 3, else: 4

    {{r, c}, if(countoccupied > maxfree, do: @empty, else: @occupied)}
  end

  def seatround({numrows, numcols, grid} = area, part) do
    newgrid =
      for col <- 0..(numcols - 1),
          row <- 0..(numrows - 1) do
        change({row, col, Map.get(grid, {row, col})}, area, part)
      end
      |> Enum.reduce(%{}, fn {k, v}, acc -> Map.put(acc, k, v) end)

    {numrows, numcols, newgrid}
  end

  def countoccupied({numrows, numcols, grid}) do
    for(
      col <- 0..(numcols - 1),
      row <- 0..(numrows - 1),
      do: Map.get(grid, {row, col}) == @occupied
    )
    |> Enum.count(& &1)
  end

  def printarea({numrows, numcols, grid} = area) do
    for(col <- 0..(numcols - 1), row <- 0..(numrows - 1), do: Map.get(grid, {row, col}))
    |> Enum.chunk_every(numrows)
    |> Enum.map(&Enum.join(&1, ""))
    |> Enum.join("\n")
    |> IO.puts()

    area
  end
end

defmodule Day11 do
  alias Day11.Grid

  def readinput() do
    rows =
      File.read!("11.test.txt")
      |> String.split("\n", trim: true)

    numrows = length(rows)
    numcols = String.length(Enum.at(rows, 0))

    grid =
      rows
      |> Enum.with_index()
      |> Enum.flat_map(fn {rowstr, rownum} ->
        String.graphemes(rowstr)
        |> Enum.with_index()
        |> Enum.flat_map(fn {letter, colnum} -> %{{rownum, colnum} => letter} end)
      end)
      |> Enum.into(%{})

    {numrows, numcols, grid}
  end

  def untilstable(area, occupied, part) do
    newarea = Grid.seatround(area, part)
    newoccupied = Grid.countoccupied(newarea)

    if newoccupied == occupied, do: newoccupied, else: untilstable(newarea, newoccupied, part)
  end

  def part1(area \\ readinput()) do
    untilstable(area, Grid.countoccupied(area), 1)
  end

  def part2(area \\ readinput()) do
    untilstable(area, Grid.countoccupied(area), 2)
  end
end

faried

faried

I can replace most of that crunk with

  def move(-1, _col, _delta, _area), do: {-1, -1}
  def move(_row, -1, _delta, _area), do: {-1, -1}

  def move(row, _col, _delta, {numrows, _, _}) when row == numrows, do: {-1, -1}
  def move(_row, col, _delta, {_, numcols, _}) when col == numcols, do: {-1, -1}

  def move(row, col, {dx, dy} = delta, {_, _, grid} = area) do
    newrow = row + dx
    newcol = col + dy

    if seat?(newrow, newcol, grid), do: {newrow, newcol}, else: move(newrow, newcol, delta, area)
  end

  def dirs(row, col, area, 2 = _part) do
    [
      move(row, col, {0, -1}, area),
      move(row, col, {0, 1}, area),
      move(row, col, {-1, 0}, area),
      move(row, col, {1, 0}, area),
      move(row, col, {-1, -1}, area),
      move(row, col, {-1, 1}, area),
      move(row, col, {1, -1}, area),
      move(row, col, {1, 1}, area),
    ]
  end
LostKobrakai

LostKobrakai

Most of those computations involving coordinates can be done with vector math and {x, y} is basically the simplest form of representing an vector.

Hallski

Hallski

Ended up putting all the seats in a map and then lookup them up to calculate occupied seats. Also learnt the lesson of making sure to copy the entire test data instead of all but the last ten lines… :slight_smile:

Anyone know of a nicer way to iterate a stream until it stabilises, what I ended up with felt a bit cumbersome.

# Part 2
defmodule AdventOfCode.Day11 do
  def run() do
    AdventOfCode.Helpers.Data.read_from_file("day11.txt")
    |> to_room()
    |> Stream.iterate(&iterate/1)
    |> Stream.chunk_every(2, 1)
    |> Stream.filter(fn [new, last] -> new == last end)
    |> Enum.take(1)
    |> (fn [[x, _]] -> x end).()
    |> occupied_seats()
  end

  def occupied_seats(%{seats: seats}) do
    seats
    |> Enum.filter(fn {_, state} -> state == "#" end)
    |> Enum.count()
  end

  def to_room(lines) do
    seats = lines |> Enum.with_index() |> Enum.map(&create_row/1) |> Enum.reduce(&Map.merge/2)

    %{width: String.graphemes(hd(lines)) |> Enum.count(), height: Enum.count(lines), seats: seats}
  end

  def create_row({line, row_nr}) do
    line
    |> String.graphemes()
    |> Enum.with_index()
    |> Enum.filter(fn {spot, _} -> spot != "." end)
    |> Enum.reduce(%{}, fn {spot, col}, acc -> Map.put(acc, {row_nr, col}, spot) end)
  end

  def iterate(%{seats: seats} = room) do
    new_seats =
      seats
      |> Enum.map(fn {{row, col} = pos, state} -> {pos, update_room(room, row, col, state)} end)
      |> Map.new()

    %{room | seats: new_seats}
  end

  def update_room(room, row, col, state) do
    update_room(occupied(room, row, col), state)
  end

  def update_room(occupied, state) when occupied == 0 and state == "L", do: "#"
  def update_room(occupied, state) when occupied >= 5 and state == "#", do: "L"
  def update_room(_, state), do: state

  def occupied(room, row, col) do
    ["N", "E", "S", "W", "NW", "NE", "SE", "SW"]
    |> Enum.map(fn direction -> occupied(room, direction, row, col) end)
    |> Enum.reduce(&(&1 + &2))
  end

  def check_dir(%{height: height, width: width}, _, row, col)
      when row < 0 or row >= height or col < 0 or col >= width do
    0
  end

  def check_dir(%{seats: seats} = room, direction, row, col) do
    case Map.get(seats, {row, col}) do
      nil -> occupied(room, direction, row, col)
      "#" -> 1
      "L" -> 0
    end
  end

  def occupied(room, "N", row, col), do: check_dir(room, "N", row - 1, col)
  def occupied(room, "E", row, col), do: check_dir(room, "E", row, col + 1)
  def occupied(room, "S", row, col), do: check_dir(room, "S", row + 1, col)
  def occupied(room, "W", row, col), do: check_dir(room, "W", row, col - 1)
  def occupied(room, "NW", row, col), do: check_dir(room, "NW", row - 1, col - 1)
  def occupied(room, "NE", row, col), do: check_dir(room, "NE", row - 1, col + 1)
  def occupied(room, "SE", row, col), do: check_dir(room, "SE", row + 1, col + 1)
  def occupied(room, "SW", row, col), do: check_dir(room, "SW", row + 1, col - 1)
end

Where Next? Top

Trending in Challenges Top

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews