exists

exists

Advent of Code 2023 - Day 24

After looking up the formula for calculating an intersection of two lines on wikipedia, part 1 was straightforward. I really do not know how would I go about solving part 2 in a programming language like elixir… I am hoping someone here would have a better idea than what I’ve done:

I did part 2 basically by a direct calculation: the observation is that already any three of the given lines determine the starting position and the direction of the rock throw (assuming of course that a global solution exists!). So I took three lines, worked out three equations with three variables (non-linear though) that determine what’s happening, asked wolfram alpha to find a solution, and calculated the starting point.

code for part 1
defmodule Main do
  def run() do
    get_input() |> Enum.map(&parse/1) |> solve1()
  end

  def get_input() do
    # "testinput24"
    "input24"
    |> File.read!() |> String.trim() |> String.split("\n")
  end

  def parse(l) do
    for e <- (l |> String.split(" @ ")) do
      e |> String.split(", ")
      |> Enum.map(&String.trim/1)
      |> Enum.map(fn s -> String.to_integer(s) end)
      |> List.to_tuple()
    end
  end

  def line_intersection_2d([{x0,y0,_},{u0,v0,_}],[{x1,y1,_},{u1,v1,_}]) do
    if u0 * v1 - u1 * v0 == 0 do {:parallel, nil}
    else
      t = ((x1-x0)*v1 - (y1-y0)*u1) / (u0*v1-v0*u1)
      s = ((x1-x0)*v0 - (y1-y0)*u0) / (u0*v1-v0*u1)
      cond do
        t < 0 or s < 0 -> {:past, nil}
        true -> {:fine, { x0 + t*u0, y0 + t*v0 } }
      end
    end
  end

  def check_bbox_2d({:fine, {x,y}}) do
    # {test_l,test_u} = {7,27}
    {test_l,test_u} = {200000000000000,400000000000000}
    test_l <= x and x <= test_u and test_l <= y and y <= test_u
  end
  def check_bbox_2d(_), do: false

  def solve1(ls) do
    for {l1,i} <- Enum.with_index(ls), {l2,j} <- Enum.with_index(ls), i<j do
      line_intersection_2d(l1,l2)
      |> check_bbox_2d()
    end
    |> Enum.filter(fn t -> t end) |> Enum.count()
  end
end

:timer.tc(&Main.run/0) |> IO.inspect(charlists: :as_lists)

Most Liked

antoine-duchenet

antoine-duchenet

Here is my solution, which takes advantage of the discrete time constraint. It is basically the Chinese Remainder Theorem applied to each axis :

  defp part2(input) do
    hailstones = parse_input(input)

    x = hailstones |> Snap.map(&Vec3.x/1) |> axis_congruence()
    y = hailstones |> Snap.map(&Vec3.y/1) |> axis_congruence()
    z = hailstones |> Snap.map(&Vec3.z/1) |> axis_congruence()

    x + y + z
  end

  defp axis_congruence(axis_hailstones) do
    -@velocity_boundary..@velocity_boundary
    |> Stream.flat_map(fn rock_velocity ->
      axis_hailstones
      |> Enum.reject(&(Snap.velocity(&1) == rock_velocity))
      |> Enum.map(&(&1 |> Snap.position() |> Cong.new(Snap.velocity(&1) - rock_velocity)))
      |> Enum.reduce([], fn congruence, coprimes ->
        if Enum.all?(coprimes, &(&1 |> Cong.m() |> Integer.gcd(Cong.m(congruence)) == 1)) do
          [congruence | coprimes]
        else
          coprimes
        end
      end)
      |> :crt.chinese_remainder()
      |> case do
        :undefined ->
          []

        rock_position
        when rock_position < -@position_boundary or rock_position > @position_boundary ->
          []

        rock_position ->
          if Enum.all?(
               axis_hailstones,
               &will_collide?(&1, Snap.new(rock_position, rock_velocity))
             ) do
            [rock_position]
          else
            []
          end
      end
    end)
    |> Enum.at(0)
  end

  defp will_collide?(hailstone, rock) do
    maybe_null_delta_position = Snap.position(hailstone) - Snap.position(rock)
    maybe_null_delta_velocity = Snap.velocity(rock) - Snap.velocity(hailstone)

    case {maybe_null_delta_position, maybe_null_delta_velocity} do
      {0, 0} ->
        true

      {_, 0} ->
        false

      {delta_position, delta_velocity} ->
        time = delta_position / delta_velocity
        time > 0 and time == round(time)
    end
  end

I allowed myself to use an Erlang implementation of the CRT to save me some time during Christmas days, but its Elixir translation should not be a problem.

Vec3, Cong and Snap are basic helper modules used to manipulate {x, y, z}, {modulo, remainder} and {position, velocity} tuples and make the code more readable.

It uses ranges of velocities and positions that I consider plausible for this exercise (essentially by looking at input magnitudes).

The tricky part (at least in my input) is that the rock can have the same position and/or velocity that some hailstones. It has to be considered to choose the inputs used for the CRT and to build the scenarios which will lead to a future collision or not.

I’m not so sure that it will solve the problem for any input, but it did the job for me !

bjorng

bjorng

Erlang Core Team

I gave up on trying to solve part 2 by myself and looked for hints on reddit. Here is my solution:

Where Next?

Popular in Challenges Top

bjorng
This topic is about Day 17 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/l...
New
DmitriyChernyavskiy
Hello everyone, I’m a new in elexir and functional language. I’m trying to implement Websocket interraction with server. On first layer...
New
shritesh
This was way too easy after the last few days. Simple map, filter and count.
New
New
maennchen
Ok, that was a rough one today. I haven’t found a way to improve the algorithm further. Part 1 runs in .5 seconds, Part 2 in ~ 5 minutes...
New
bjorng
Note: This topic is to talk about Day 9 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can join...
New
christhekeele
Setting this down for the night, as after a quick naive solve for quick part 1 I realize that part 2 is by design computationally expensi...
New
New
code-shoily
Here’s my day 3 code This was quite easy. I was afraid Part 2 would be “un-regex-able” and was preparing for hand crafting automata bu...
New
coen.bakker
Since I started using Elixir, I have benefited greatly from being able to study various open-source projects. The codebase of LiveBook, i...
New

Other popular topics Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New

We're in Beta

About us Mission Statement