kwando

kwando

Took a while, but another use case for “move vectors” today and pattern matching. :slight_smile:

The trick was to first generate a list of position for the head with the help of the instructions.
Then you you use that list to move the tail, and repeat that process for as many knots there are.. a perfect job for Enum.scan :slight_smile:

Quite happy with this solution.

https://github.com/kwando/AoC2022/blob/main/09/day09.livemd

Showing Posts 1 to 10

adamu

adamu

You’re not supposed to share the problem text or input according to the AoC ToS. Although that’s probably a lost cause by now :see_no_evil_monkey:

Enjoyed this one. Part 2 took me by surprise, but it was fun. I managed to reuse the functions from part 1, just updated motion function to keep track of the intermediary knots in a list, with map_reduce to simultaneously enumerate the knots + track the previous one.

  defp step_head({x, y}, "R"), do: {x + 1, y}
  defp step_head({x, y}, "L"), do: {x - 1, y}
  defp step_head({x, y}, "U"), do: {x, y + 1}
  defp step_head({x, y}, "D"), do: {x, y - 1}

  defp step_tail({hx, hy}, {tx, ty}) when abs(hx - tx) <= 1 and abs(hy - ty) <= 1, do: {tx, ty}
  defp step_tail({tx, hy}, {tx, ty}) when hy - ty > 0, do: {tx, ty + 1}
  defp step_tail({tx, hy}, {tx, ty}) when hy - ty < 0, do: {tx, ty - 1}
  defp step_tail({hx, ty}, {tx, ty}) when hx - tx > 0, do: {tx + 1, ty}
  defp step_tail({hx, ty}, {tx, ty}) when hx - tx < 0, do: {tx - 1, ty}
  defp step_tail({hx, hy}, {tx, ty}) do
    dx = if hx - tx > 0, do: 1, else: -1
    dy = if hy - ty > 0, do: 1, else: -1
    {tx + dx, ty + dy}
  end

https://git.adamu.jp/adam/AdventOfCode/src/branch/main/2022/day9.exs

adamu

adamu

@kwando That’s a nice use of Enum.scan. As you said, perfect for the task :slight_smile:

Arzar

Arzar

It was really fun coding this ̶s̶n̶a̶k̶e̶ rope-thing simulator (code below is part 2)

defmodule Day9 do

  def move_head({x, y}, "L"), do: {x - 1, y}
  def move_head({x, y}, "R"), do: {x + 1, y}
  def move_head({x, y}, "U"), do: {x, y + 1}
  def move_head({x, y}, "D"), do: {x, y - 1}

  def move_toward(prev, current) do
    if prev - current > 0, do: current + 1, else: current - 1
  end

  def move_knot({x_prev, y_prev}, {x, y}) do
    cond do
      abs(x_prev - x) <= 1 and abs(y_prev - y) <= 1 -> {x, y}  # adjacent
      x == x_prev -> {x, move_toward(y_prev, y)}               # same line
      y == y_prev -> {move_toward(x_prev, x), y}               # same column
      true -> {move_toward(x_prev, x), move_toward(y_prev, y)} # not touching
    end
  end

  def start() do

    moves = File.stream!("input09")
    |> Stream.map(&String.trim/1)
    |> Enum.map(&String.split/1)
    |> Enum.flat_map(fn [dir, step] -> for _ <-1..String.to_integer(step), do: dir end)

    knots = for _ <- 1..9, do: {0, 0}

    {_, _, tail_history} = Enum.reduce(moves , {{0, 0}, knots, []}, fn dir, {head, knots, tail_history} ->
      head = move_head(head, dir)
      {knots, tail} = Enum.map_reduce(knots, head, fn knot, prev_knot ->
        knot = move_knot(prev_knot, knot)
        {knot, knot}
      end)

      {head, knots, [tail | tail_history]}
    end)

    tail_history |> Enum.uniq |> Enum.count
  end
end
kwando

kwando OP

Oh, didnt know that. Gonna stop adding that to my livebooks :slight_smile: thanks for the heads up!

Joseph

Joseph

milli

milli

After a lot of refactoring I’m solving both parts using the same function and I’m happy with my code :slight_smile:

  defp move([dir | rest], [head | knots], tail_path) do
    new_head = move_head(dir, head)
    new_rope = Enum.scan([new_head | knots], fn tail, head -> follow(head, tail) end)
    move(rest, new_rope, MapSet.put(tail_path, Enum.at(new_rope, -1)))
  end

Full solution on GitHub

adamu

adamu

You are expanding out the directions, right? So if the head moves up 10k in one movement, you will have 10k up instructions in your list. And if another rope snaps and there are more knots, that’s a lot of work to traverse the whole rope to find the last one each time. Some points to consider if you want to spend more time refactoring :grinning_face_with_smiling_eyes:

stevensonmt

stevensonmt

I spent a lot of time refactoring my initial part 1 solution after reading the part 2 prompt and realizing I should be able to reuse the solution for both with the rope length as a parameter. Mostly had to change my data structure from %{head: {x,y}, tail: {m,n}, visited: MapSet<>} to %{rope: [], visited: MapSet<>}. I’m still not pleased about reversing the list of knots on every move. Curious to see how others managed to avoid that.
https://github.com/stevensonmt/advent_of_code/blob/2022/2022/day9/lib/day9.ex

LostKobrakai

LostKobrakai

This was actually more fun than I initially expected having read part 1 yesterday and opting to skip it till the weekend. I did need to refactor much for part 2. Just needed to replace reducing over {0, 0} and a single move with List.duplicate({0, 0}, 9) and doing the move over the list as well. List.duplicate together with Enum.flat_map also turned out to be useful for the instruction list.

Solution
defmodule Day9 do
  # {0, 0} is in left/bottom corner
  def parse(text) do
    ~r/^([RULD]) (\d+?)$/m
    |> Regex.scan(text)
    |> Enum.flat_map(fn [_full, direction, amount] ->
      amount = String.to_integer(amount)

      dir =
        case direction do
          "R" -> List.duplicate({1, 0}, amount)
          "L" -> List.duplicate({-1, 0}, amount)
          "U" -> List.duplicate({0, 1}, amount)
          "D" -> List.duplicate({0, -1}, amount)
        end
    end)
  end

  def count_followed_places(text, tail_segments \\ 1)
      when tail_segments >= 1 do
    movements = parse(text)

    movements
    # Add start
    |> List.insert_at(0, {0, 0})
    |> Enum.map_reduce({0, 0}, fn {delta_x, delta_y}, {x, y} ->
      next = {x + delta_x, y + delta_y}
      {next, next}
    end)
    |> elem(0)
    |> Enum.map_reduce(List.duplicate({0, 0}, tail_segments), fn head, tails ->
      tails =
        Enum.map_reduce(tails, head, fn tail, head ->
          next = calculate_next(head, tail)
          {next, next}
        end)
        |> elem(0)

      {List.last(tails), tails}
    end)
    |> elem(0)
    |> Enum.uniq()
    |> Enum.count()
  end

  def calculate_next(head, tail) do
    case {head, tail} do
      # Touching or on top of each other
      {{head_x, head_y}, {tail_x, tail_y}}
      when abs(head_x - tail_x) <= 1 and abs(head_y - tail_y) <= 1 ->
        {tail_x, tail_y}

      # Move into direction of head
      {{head_x, head_y}, {tail_x, tail_y}} ->
        {
          tail_x + direction(head_x, tail_x),
          tail_y + direction(head_y, tail_y)
        }
    end
  end

  defp direction(a, b) when a == b, do: 0
  defp direction(a, b) when a > b, do: 1
  defp direction(a, b) when a < b, do: -1
end
milli

milli

I forgot to mention I was refactoring mainly for beauty :wink: It looks like we’ll get to enjoy enough of 10k optimizing in Day 11, though :smile:

Where Next? Top

Trending in Challenges Top

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
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
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews