Skip to content

Instantly share code, notes, and snippets.

@gjastrab
Last active December 5, 2018 05:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gjastrab/a86871cdad8e76911009c1adb7122064 to your computer and use it in GitHub Desktop.
Save gjastrab/a86871cdad8e76911009c1adb7122064 to your computer and use it in GitHub Desktop.
Advent of Code 2018 - Elixir
defmodule Advent2018.Frequencies do
@input_file "frequencies"
def calibrate do
{:ok, data} = File.read(@input_file)
data
|> split_lines()
|> Enum.map(&line_to_number/1)
|> Enum.sum()
end
defp split_lines(data) do
data
|> String.trim()
|> String.split("\n")
end
defp sum(lines) do
lines
|> Enum.reduce(0, fn num, total ->
total + line_to_number(num)
end)
end
defp line_to_number(line), do: line |> String.trim() |> String.to_integer()
def calibrate_repeated do
{:ok, data} = File.read(@input_file)
data
|> split_lines()
|> Stream.cycle()
|> find_repeated_frequency()
end
defp find_repeated_frequency(stream) do
stream
|> Enum.reduce_while({0, MapSet.new()}, fn num, {total, set} ->
new_total = total + line_to_number(num)
if MapSet.member?(set, new_total) do
{:halt, new_total}
else
{:cont, {new_total, MapSet.put(set, new_total)}}
end
end)
end
end
Advent2018.Frequencies.calibrate() |> IO.puts()
Advent2018.Frequencies.calibrate_repeated() |> IO.puts()
defmodule Advent2018.Day2 do
def part1(stream) do
stream
|> Stream.map(&to_freq_tuple/1)
|> Enum.reduce({0,0}, fn {x,y}, {twos, threes} ->
{x+twos, y+threes}
end)
|> Tuple.to_list()
|> Enum.reduce(&*/2)
end
@spec to_freq_tuple(String.t()) :: {number(), number()}
defp to_freq_tuple(line) do
import Bitwise, only: [bor: 2]
line
|> String.graphemes()
|> Enum.reduce(%{}, fn char, map ->
Map.update(map, char, 1, &(&1 + 1))
end)
|> Enum.reduce({0, 0}, fn {_,v}, {twos, threes} ->
{twos,threes}
{bor(twos, bool_to_i(v == 2)),
bor(threes, bool_to_i(v == 3))}
end)
end
defp bool_to_i(expr), do: expr && 1 || 0
def part2(stream) do
stream
|> Stream.map(&(&1 |> String.trim |> String.to_charlist()))
# -> [ [1,2,3,4,5,6], [2,1,2,1,2,3], ... ]
|> Enum.to_list()
|> find_lists_with_single_diff()
# -> {[1,2,3], [1,3,3]}
|> combine_common_chars()
end
def combine_common_chars({list1, list2}) do
Enum.zip(list1, list2)
|> Enum.reduce([], fn pair, acc ->
case pair do
{x,x} -> [x | acc]
_ -> acc
end
end)
|> Enum.reverse()
end
def find_lists_with_single_diff([_ | rest] = list) do
Enum.reduce_while(
list,
rest,
fn cur_list, remaining ->
case Enum.find(remaining, fn x -> diff_between(cur_list, x) == 1 end) do
nil ->
[_ | new_remaining] = remaining
{:cont, new_remaining}
diff ->
{:halt, {cur_list, diff}}
end
end
)
end
@doc """
Given 2 lists of equal length returns the number of
positional differences between the lists.
"""
def diff_between(list1, list2) when is_list(list1) and is_list(list2)
and length(list1) == length(list2) do
Enum.zip(list1, list2)
|> Enum.reduce(0,
fn pair, acc ->
case pair do
{x,x} -> acc
_ -> acc + 1
end
end
)
end
def diff_between(_, _), do: raise "diff_between must be called with 2 equal length lists"
end
# Thanks Jose for Day1 pattern to easily switch
# between test running and real input!
case System.argv() do
["--test"] ->
ExUnit.start()
defmodule Advent2018.Day2Test do
use ExUnit.Case
import Advent2018.Day2
@testdata """
abcdef
bababc
abbcde
abcccd
aabcdd
abcdee
ababab
"""
test "part1 example IDs checksum is 12" do
{:ok, io} = StringIO.open(@testdata)
stream = IO.stream(io, :line)
assert part1(stream) == 12
end
test "part2 common letters" do
{:ok, io} = StringIO.open("""
abcde
fghij
klmno
pqrst
fguij
axcye
wvxyz
""")
stream = IO.stream(io, :line)
assert part2(stream) == String.to_charlist("fgij")
end
test "diff_between/2 returns the number of items which differ position-by-position between 2 lists" do
assert diff_between([1,1], [1,1]) == 0
assert diff_between([1,0], [1,1]) == 1
assert diff_between([1,0], [0,1]) == 2
end
test "diff_between/2 only works between 2 equal lists" do
assert_raise RuntimeError, fn ->
diff_between([1], [2,3])
end
end
test "find_lists_with_single_diff/1 returns our matching tuple" do
assert find_lists_with_single_diff([ [1,2,1], [0,1,2], [0,1,3] ]) ==
{[0,1,2], [0,1,3]}
end
end
["-p2 "<>filename] ->
filename
|> File.stream!([], :line)
|> Advent2018.Day2.part2()
|> IO.inspect()
[filename] ->
filename
|> File.stream!([], :line)
|> Advent2018.Day2.part1()
|> IO.inspect()
_ ->
IO.puts(:stderr, "must be called with --test or a filename")
System.halt(1)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment