Advent of Code 2021 - Day 22

This topic is about Day 22 of the Advent of Code 2021.

We have a private leaderboard (shared with users of the elixir forum):

https://adventofcode.com/2021/leaderboard/private/view/370884

The entry code is:
370884-a6a71927

3 Likes

My solution for day 22; only part1 really works; having a look at the elixer forum there seems to be some range function used by all solutions there at least for part2.

I am not sure if Diagraph could be used for part 2 or if some way of recording of intersections could be used for providing a solution to part2. Or possibly just some mathematical function could be used; would be keen if anyone tries it to see how it could be done.

-module(day_22). 

-compile([export_all]).

-record(inst, {set, 
               x_range = {from,to}, 
               y_range = {from,to},
               z_range = {from,to}
              }).

p1() ->
    Inst = input(), 
    ValidInst  = validate_inst(Inst),
    %%    {Inst, ValidInst}.
    MapResult = make_map(ValidInst, #{}),
    lists:sum([1|| {_,X} <- maps:to_list(MapResult),
                   X == on
              ]).

p2() ->
    Inst = input(),
    MapResult = make_map2(Inst, #{}),
    lists:sum([1|| {_,X} <- maps:to_list(MapResult),
                   X == on
              ]).



make_map([], Map) ->
    Map;
make_map([#inst{set = Set, 
                x_range = {XF, XT},
                y_range = {YF, YT},
                z_range = {ZF, ZT}}|TailInst], 
         Map) ->
    Coords = [{X, Y, Z} || X <- lists:seq(XF, XT), 
                           Y <- lists:seq(YF,YT),
                           Z <- lists:seq(ZF,ZT), 
                           X >= -50, X =< 50, 
                           Y >= -50, Y =< 50,
                           Z >= -50, Z =< 50],
    NewMap = set_coords(Coords, Set, Map),
    io:format("1~n", []),
    make_map(TailInst, NewMap).

make_map2([], Map) ->
    Map;
make_map2([#inst{set = Set, 
                 x_range = {XF, XT},
                 y_range = {YF, YT},
                 z_range = {ZF, ZT}}|TailInst],
          Map) ->
    Coords = [{X, Y, Z} || X <- lists:seq(XF, XT),
                           Y <- lists:seq(YF,YT),
                           Z <- lists:seq(ZF,ZT),
                           X >= -50, X =< 50,
                           Y >= -50, Y =< 50,
                           Z >= -50, Z =< 50],
    NewMap = set_coords(Coords, Set, Map),
    io:format("1~n", []),
    make_map2(TailInst, NewMap).


set_coords([], _, Map) ->
    Map;
set_coords([E|Tail],Set, Map) ->
    set_coords(Tail, Set, 
               maps:put(E,Set, Map)
              ).




validate_inst([]) ->
    [];
validate_inst([I = #inst{
                      x_range = {XF, XT},
                      y_range = {YF, YT},
                      z_range = {ZF, ZT}
                     }
               | Tail]) ->
    %% Validate Range resonable. 
    %%
    case {valid(XF, XT), valid(YF, YT), valid(ZF, ZT)} of
        {valid, valid, valid} ->
            [I|validate_inst(Tail)];
        _ ->
            validate_inst(Tail)
    end.

valid(A, B) when A > 50 , B > 50 ;
                 A < -50, B < -50 ->
    invalid;
valid(_,_) ->
    valid.



%% Parse Input
input() ->
    {ok, Bin} = file:read_file("priv/input.txt"), 
    Lines =  string:tokens(binary_to_list(Bin), "\n"),
    parse_instructions(Lines).


parse_instructions([]) -> [];
parse_instructions([E|Tail]) ->
    {Inst, Rest} = case string:tokens(E, " ") of
                       ["on",Rest] ->
                           {on,Rest};
                       ["off",Rest] ->
                           {off, Rest}
                   end,
    [XF, XT, YF, YT, ZF, ZT] =  string:tokens(Rest, "x=yz.,"),
    [#inst{set = Inst, 
           x_range = {list_to_integer(XF), list_to_integer(XT)},
           y_range = {list_to_integer(YF), list_to_integer(YT)},
           z_range = {list_to_integer(ZF), list_to_integer(ZT)}
          }|
     parse_instructions(Tail)].

4 Likes

The Elixir Range type can easily be implemented in Erlang using a tuple: {From, To}. Thus, the range 1..10 would be represented as the tuple {1, 10}. To get the size of a range implemented in that way, use:

range_size({From, To}) ->
    max(To - From + 1, 0).

UPDATED: To test for disjoint ranges, use:

range_are_disjoint({From1, To1}, {From2, To2}) ->
    To2 < From1 orelse To1 < From2.
5 Likes

a rare (for me) occasion to put a solution into one screen of code

2 Likes

bjorng, thanks for present this AoC. These challenges are an interesting option to try what I already learned and learn from the experts.

I didn’t expected get the code running slower when using sets instead of ordsets.
But, I got the right answer - for the part one of the challenge. For the second one I got a huge memory crash. :slightly_smiling_face:

-module(aoc2021_d22).

-export([main/0]).

-define(LO_RANGE, -50).
-define(HI_RANGE, +50).

main() ->
    Data = get_data(),
    CubesOn = reboot_proc(Data),
    io:format("Cubes turned On: ~p\n",[ordsets:size(CubesOn)]).

get_data() ->
    case file:read_file("aoc2021_d22-input.txt") of
        {ok, File} -> 
            Buff = string:split(unicode:characters_to_list(File),"\n",all),
            parse_data(Buff,[]);
        _ -> []
    end.

reboot_proc(Data) ->
    reboot_proc(Data, ordsets:new()).

reboot_proc([], Acc) -> Acc;
reboot_proc([Row|Rows], Acc0) ->
    Acc = reboot_step(Row, Acc0),
    reboot_proc(Rows, Acc).

reboot_step({_, []}, Acc) -> Acc;
reboot_step({on, [X,Y,Z]}, Acc0) ->
    Acc = calc_cuboid(X,Y,Z,[]),
    Acc1 = ordsets:from_list(Acc),
    ordsets:union(Acc0,Acc1);
reboot_step({off, [X,Y,Z]}, Acc0) ->
    Acc = calc_cuboid(X,Y,Z,[]),
    Acc1 = ordsets:from_list(Acc),
    ordsets:subtract(Acc0,Acc1).

calc_cuboid({X0,X1}, _, _, Acc) when X0 > X1 -> Acc;
calc_cuboid({X0,X1}=X, Y, Z, Acc0) ->
    Acc = calc_cuboid_y(X,Y,Z,Acc0),
    calc_cuboid({X0+1,X1},Y,Z,Acc).
    
calc_cuboid_y(_, {Y0,Y1}, _, Acc) when Y0 > Y1 -> Acc;
calc_cuboid_y(X, {Y0,Y1}=Y, Z, Acc0) ->
    Acc = calc_cuboid_z(X,Y,Z, Acc0),
    calc_cuboid_y(X,{Y0+1,Y1},Z,Acc).

calc_cuboid_z(_, _, {Z0,Z1}, Acc) when Z0 > Z1 -> Acc;
calc_cuboid_z({X0,_}=X, {Y0,_}=Y, {Z0,Z1}, Acc0) ->
    calc_cuboid_z(X,Y,{Z0+1,Z1},[{X0,Y0,Z0}|Acc0]).

validate_range({Lo,Hi}) ->
    if ((Lo < ?LO_RANGE) and (Hi < ?LO_RANGE)) or
       ((Lo > ?HI_RANGE) and (Hi > ?HI_RANGE)) ->
       false;
    true ->
        {min(max(Lo,?LO_RANGE),?HI_RANGE),
         max(min(Hi,?HI_RANGE),?LO_RANGE)}
    end.

parse_data([], Acc) -> 
    lists:reverse(Acc);
parse_data([[]], Acc) -> 
    parse_data([], Acc);
parse_data([Data|Buff], Acc) ->
    parse_data(Buff, [parse_row(Data)|Acc]).
    
parse_row(Row) ->
    [Cmd,Dim] = string:split(Row," "),
    {list_to_atom(Cmd),parse_values(Dim)}.
    
parse_values(Dim) ->
    DimValues = string:split(Dim,",", all),
    parse_value(DimValues,[]).

parse_value([], Acc) -> Acc;
parse_value([DimValue|DimValues], Acc) ->
    [_Coord,Range] = string:split(DimValue,"="),
    case string:split(Range,"..") of
        [Lo0,Hi0] ->
            case validate_range({list_to_integer(Lo0),list_to_integer(Hi0)}) of
                {Lo,Hi} -> parse_value(DimValues,[{Lo,Hi}|Acc]);
                _ -> []
            end;
        _ -> [] 
    end.
3 Likes

You will get better performance for sets if you use the {version, 2} option when creating the sets:

    Acc1 = sets:from_list(Acc, [{version, 2}]),

Here is the documentation for {version, 2}:

Erlang/OTP 24.0 introduced a new internal representation for sets which is more performant. Developers can use this new representation by passing the {version, 2} flag to new/1 and from_list/2, such as sets:new([{version, 2}]). This new representation will become the default in future Erlang/OTP versions.

3 Likes