DEV Community

Discussion on: AoC Day 1: Chronal Calibration

Collapse
 
milmazz profile image
Milton Mazzarri

Thanks for sharing, here are my solutions in Elixir and Racket.

I'm not sure if the Racket code is idiomatic, so, any advice is more than welcome.

Elixir

# https://adventofcode.com/2018/day/1
#
# To run each exercise, you can do the following:
#
# elixir -r exercise.exs -e "IO.inspect(Frequency.first_exercise())"
# elixir -r exercise.exs -e "IO.inspect(Frequency.second_exercise())"
#
defmodule Frequency do
  def first_exercise, do: frequency(process_file())

  def second_exercise, do: first_frequency_reached_twice(process_file())

  @spec frequency([integer]) :: integer
  def frequency(freq_changes), do: Enum.sum(freq_changes)

  @spec first_frequency_reached_twice([integer], {integer, MapSet.t()} | integer) :: integer
  def first_frequency_reached_twice(frequency_changes, acc \\ {0, MapSet.new([0])})

  def first_frequency_reached_twice(_, acc) when is_integer(acc), do: acc

  def first_frequency_reached_twice(frequency_changes, acc) do
    result =
      Enum.reduce_while(frequency_changes, acc, fn digit, {current, past} ->
        next = current + digit

        if MapSet.member?(past, next) do
          {:halt, next}
        else
          {:cont, {next, MapSet.put(past, next)}}
        end
      end)

    first_frequency_reached_twice(frequency_changes, result)
  end

  defp process_file do
    "input"
    |> File.stream!()
    |> Stream.map(fn x -> x |> String.trim() |> String.to_integer() end)
    |> Enum.to_list()
  end
end

ExUnit.start()

defmodule FrequencyTest do
  use ExUnit.Case

  import Frequency

  test "should calculate frequency" do
    test_cases = [
      {[1, -2, 3, 1], 3},
      {[1, 1, 1], 3},
      {[1, 1, -2], 0},
      {[-1, -2, -3], -6}
    ]

    Enum.each(test_cases, fn {changes, expected} ->
      assert frequency(changes) == expected
    end)
  end

  test "should stop when a frequency is reached twice" do
    test_cases = [
      {[1, -2, 3, 1, 1, -2], 2},
      {[1, -1], 0},
      {[3, 3, 4, -2, -4], 10},
      {[-6, 3, 8, 5, -6], 5},
      {[7, 7, -2, -7, -4], 14}
    ]

    Enum.each(test_cases, fn {changes, expected} ->
      assert first_frequency_reached_twice(changes) == expected
    end)
  end
end

Racket

#lang racket/base

#|
Reference: https://adventofcode.com/2018/day/1

If you want to test the results:

λ racket
> (require (file "exercise.rkt"))
> (frequency (file->list "input"))
520
> (first-frequency-reached-twice (file->list "input"))
394
|#

(require racket/set)
(require racket/match)

(provide frequency first-frequency-reached-twice)

(define (frequency changes)
  (foldl + 0 changes))

(define (first-frequency-reached-twice changes)
  (find-frequency (cons 0 (set 0)) changes))

(define (find-frequency acc changes)
  (match acc
    [result
     #:when (integer? result)
     result]
    [(cons current previous-frequencies)
     (find-frequency (reduce-while changes previous-frequencies current) changes)]))

(define (reduce-while changes previous-frequencies current)
  (match changes
    [changes
     #:when (eq? '() changes)
     (cons current previous-frequencies)]
    [(cons head tail)
     (define next (+ current head))
     (if (set-member? previous-frequencies next)
         next
         (reduce-while tail (set-add previous-frequencies next) next))]))

Unit tests for Racket:

#lang racket/base

(require rackunit
         "exercise.rkt")

(define exercise-tests
  (test-suite
   "Tests for exercise day 1"

   (test-case
    "Should calculate frequency"

    (check-equal? (frequency '(1 -2 3 1)) 3)
    (check-equal? (frequency '(1 1 1)) 3)
    (check-equal? (frequency '(1 1 -2)) 0)
    (check-equal? (frequency '(-1 -2 -3)) -6))

   (test-case
    "Should find first frequency reached twice"

    (check-equal? (first-frequency-reached-twice '(1 -1)) 0)
    (check-equal? (first-frequency-reached-twice '(1 -2 3 1 1 -2)) 2)
    (check-equal? (first-frequency-reached-twice '(3 3 4 -2 -4)) 10)
    (check-equal? (first-frequency-reached-twice '(-6 3 8 5 -6)) 5)
    (check-equal? (first-frequency-reached-twice '(7 7 -2 -7 -4)) 14))))

(require rackunit/text-ui)
(run-tests exercise-tests)