-module(day15). -export([solve/1]). solve(Input) -> {part1(Input), part2(Input)}. part1(Input) -> Sensors = parse_sensors(Input), Ranges = get_row(Sensors, 2000000), BeaconlessRanges = remove_beacons(Sensors, 2000000, Ranges), sum_ranges(BeaconlessRanges). part2(Input) -> Sensors = parse_sensors(Input), {Sensors, [Answer]} = for(0, 4000000, fun check_row_for_gaps/2, {Sensors, []}), Answer. check_row_for_gaps(Row, {Sensors, Acc}) -> case rset_remove_after(4000001, rset_remove_before(-1, get_row(Sensors, Row))) of [{_X, _Y}] -> {Sensors, Acc}; [{0, NMinusOne}, {NPlusOne, 4000000}] when NMinusOne + 2 == NPlusOne -> {Sensors, [tuning_frequency(NMinusOne+1, Row)|Acc]} end. tuning_frequency(X, Y) -> X * 4000000 + Y. for(End, End, Fun, Acc) -> Fun(End, Acc); for(Start, End, Fun, Acc) -> for(Start+1, End, Fun, Fun(Start, Acc)). parse_sensors(Input) -> lists:map(fun parse_sensor/1, binary:split(Input, <<$\n>>, [global, trim_all])). parse_sensor(Line) -> {ok, [SensorX, SensorY, BeaconX, BeaconY], _Rest} = io_lib:fread("Sensor at x=~d, y=~d: closest beacon is at x=~d, y=~d", binary_to_list(Line)), {{SensorX, SensorY}, {BeaconX, BeaconY}}. get_row(Sensors, Row) -> get_row(Sensors, Row, rset_new()). remove_beacons([], _Row, Set) -> Set; remove_beacons([{_, {BeaconX, Row}}|Rest], Row, Set) -> remove_beacons(Rest, Row, rset_remove1(BeaconX, Set)); remove_beacons([_|Rest], Row, Set) -> remove_beacons(Rest, Row, Set). get_row([], _Row, Acc) -> Acc; get_row([Sensor | Rest], Row, Acc) -> NewAcc = case get_intersection(Sensor, Row) of none -> Acc; Range -> rset_append(Range, Acc) end, get_row(Rest, Row, NewAcc). get_intersection({{SensorX, SensorY} = Sensor, NearestBeacon}, Row) -> case vec_mag(vec_sub(Sensor, NearestBeacon)) - abs(Row - SensorY) of TooFar when TooFar < 0 -> none; Signal -> {SensorX - Signal, SensorX + Signal} end. vec_sub({LX, LY}, {RX, RY}) -> {LX - RX, LY - RY}. % vector magnitude vec_mag({X, Y}) -> abs(X) + abs(Y). sum_ranges([]) -> 0; sum_ranges([{Start, End} | Rest]) -> End - Start + 1 + sum_ranges(Rest). rset_new() -> []. % rangeset is a sorted set of discrete ranges. Adjacent ranges are merged. rset_append(Entry, []) -> [Entry]; rset_append({_, End} = Entry, [{HeadStart, _} | _] = Set) when End < HeadStart - 1 -> [Entry | Set]; rset_append({Start, _} = Entry, [{_, HeadEnd} = Head | Rest]) when Start > HeadEnd + 1 -> [ Head | rset_append(Entry, Rest)]; rset_append({Start, End}, [{HeadStart, HeadEnd} | Rest]) -> rset_append({min(Start, HeadStart), max(End, HeadEnd)}, Rest). rset_remove1(_Point, []) -> []; rset_remove1(Point, [{Start, _End} | _Rest] = Set) when Point < Start -> Set; rset_remove1(Point, [{_Start, End} = Head | Rest]) when Point > End -> [Head | rset_remove1(Point, Rest)]; rset_remove1(Point, [{Point, Point} | Rest]) -> Rest; rset_remove1(Point, [{Point, End} | Rest]) -> [{Point+1, End} | Rest]; rset_remove1(Point, [{Start, Point} | Rest]) -> [{Start, Point-1} | Rest]; rset_remove1(Point, [{Start, End} | Rest]) -> [{Start, Point-1}, {Point+1, End} | Rest]. rset_remove_before(_Point, []) -> []; rset_remove_before(Point, [{_, End}|Rest]) when Point >= End -> rset_remove_before(Point, Rest); rset_remove_before(Point, [{Start, _}|_] = List) when Point < Start -> List; rset_remove_before(Point, [{_, End}|Rest]) -> [{Point+1, End}|Rest]. rset_remove_after(_Point, []) -> []; rset_remove_after(Point, [{Start, _}|_]) when Point =< Start -> []; rset_remove_after(Point, [{_, End}=Node | Rest]) when Point > End -> [Node|rset_remove_after(Point, Rest)]; rset_remove_after(Point, [{Start, _End}|_]) -> [{Start, Point-1}].