Advent of Code 2021 in Julia – Day 5: Hydrothermal Venture

I thought the challenge today would be easy, but in the end I spent too much time on it. Around halfway through I realized that I forgot to offset the index, because Julia indexes begin at 1 instead of 0. On the other hand, I learnt how slurping and splatting with ... works, which provide shorthand for “unwrapping” arrays, to be used inside functions.

One strange occurence I found is that I have to explicitly return nothing in a function for it to work within mapslices. I haven’t figured out why that is the case. This little hack works, for now.

Part 1

Show challenge - day 5, part 1

You come across a field of hydrothermal vents on the ocean floor! These vents constantly produce large, opaque clouds, so it would be best to avoid them if possible.

They tend to form in lines; the submarine helpfully produces a list of nearby lines of vents (your puzzle input) for you to review. For example:

0,9 -> 5,9
8,0 -> 0,8
9,4 -> 3,4
2,2 -> 2,1
7,0 -> 7,4
6,4 -> 2,0
0,9 -> 2,9
3,4 -> 1,4
0,0 -> 8,8
5,5 -> 8,2

Each line of vents is given as a line segment in the format x1,y1 -> x2,y2 where x1, y1 are the coordinates of one end the line segment and x2, y2 are the coordinates of the other end. These line segments include the points at both ends. In other words:

  • An entry like 1,1 -> 1,3 covers points 1,1, 1,2, and 1,3.
  • An entry like 9,7 -> 7,7 covers points 9,7, 8,7, and 7,7.

For now, only consider horizontal and vertical lines: lines where either x1 = x2 or y1 = y2.

So, the horizontal and vertical lines from the above list would produce the following diagram:

.......1..
..1....1..
..1....1..
.......1..
.112111211
..........
..........
..........
..........
222111....

In this diagram, the top left corner is 0,0 and the bottom right corner is 9,9. Each position is shown as the number of lines which cover that point or . if no line covers that point. The top-left pair of 1s, for example, comes from 2,2 -> 2,1; the very bottom row is formed by the overlapping lines 0,9 -> 5,9 and 0,9 -> 2,9.

To avoid the most dangerous areas, you need to determine the number of points where at least two lines overlap. In the above example, this is anywhere in the diagram with a 2 or larger - a total of 5 points.

Consider only horizontal and vertical lines. At how many points do at least two lines overlap?

Here’s the provided input. It’s quite long so don’t try to scroll through:

Show input - day 5
 818,513 -> 818,849
259,377 -> 259,599
120,758 -> 977,758
49,386 -> 170,386
773,644 -> 773,745
510,958 -> 797,671
480,438 -> 14,904
475,346 -> 648,173
620,167 -> 477,310
632,756 -> 275,756
225,896 -> 432,896
582,450 -> 93,450
402,262 -> 254,410
915,236 -> 709,236
338,530 -> 338,375
243,857 -> 370,984
32,449 -> 32,26
672,832 -> 203,363
426,961 -> 426,402
108,469 -> 550,27
567,698 -> 495,770
25,872 -> 861,36
12,15 -> 960,963
986,159 -> 986,765
912,437 -> 714,437
476,400 -> 476,873
125,52 -> 951,878
633,939 -> 633,641
12,986 -> 987,11
783,204 -> 467,520
547,834 -> 413,700
879,133 -> 879,890
409,128 -> 365,128
913,404 -> 913,598
798,40 -> 841,40
279,410 -> 279,758
935,807 -> 959,783
389,391 -> 389,38
271,736 -> 271,865
70,10 -> 814,10
694,266 -> 694,92
860,128 -> 860,966
755,871 -> 608,871
519,54 -> 519,833
817,733 -> 156,72
553,558 -> 726,558
152,21 -> 830,699
529,721 -> 608,721
744,719 -> 744,221
962,966 -> 10,14
863,813 -> 863,984
138,167 -> 801,830
926,901 -> 926,769
170,630 -> 623,177
281,207 -> 510,207
41,52 -> 945,956
100,204 -> 100,43
177,588 -> 469,588
946,37 -> 201,782
669,89 -> 669,868
945,32 -> 46,931
673,274 -> 412,274
424,979 -> 169,979
774,220 -> 774,571
684,272 -> 229,272
621,458 -> 621,884
222,964 -> 222,608
953,146 -> 145,954
442,736 -> 442,230
471,341 -> 471,124
632,954 -> 968,954
492,562 -> 911,981
485,320 -> 485,349
888,758 -> 888,23
271,555 -> 732,555
932,415 -> 792,275
399,478 -> 399,472
543,774 -> 543,261
422,872 -> 422,715
524,471 -> 445,471
573,513 -> 573,762
721,39 -> 482,39
379,482 -> 379,479
70,770 -> 336,770
797,724 -> 797,364
498,646 -> 498,410
756,19 -> 756,83
901,918 -> 11,918
572,23 -> 362,23
814,563 -> 750,627
279,736 -> 249,706
509,257 -> 509,897
127,374 -> 399,102
421,139 -> 607,139
79,520 -> 166,607
491,779 -> 284,986
395,606 -> 395,164
958,244 -> 442,760
493,509 -> 673,509
76,321 -> 482,727
903,926 -> 103,126
729,689 -> 63,23
956,13 -> 198,771
973,452 -> 193,452
282,362 -> 896,976
68,985 -> 985,68
148,109 -> 310,109
852,920 -> 371,439
334,41 -> 864,571
258,592 -> 72,778
259,990 -> 259,308
480,363 -> 480,751
943,153 -> 801,153
755,172 -> 123,804
355,501 -> 437,501
666,769 -> 340,769
982,10 -> 23,969
886,410 -> 886,669
454,410 -> 454,218
426,688 -> 227,688
969,959 -> 207,959
586,27 -> 591,22
852,133 -> 166,819
587,199 -> 781,199
78,85 -> 78,394
468,162 -> 727,162
261,473 -> 573,473
924,300 -> 924,873
651,817 -> 651,600
717,909 -> 717,187
898,656 -> 454,656
805,873 -> 507,873
529,825 -> 529,217
578,503 -> 783,503
929,663 -> 920,663
332,938 -> 332,381
710,783 -> 272,783
505,863 -> 240,863
146,548 -> 146,849
71,822 -> 820,73
875,883 -> 451,883
983,17 -> 30,970
646,350 -> 646,330
618,430 -> 881,430
398,977 -> 643,732
839,441 -> 406,874
948,358 -> 178,358
26,660 -> 26,612
418,467 -> 418,115
899,936 -> 899,611
770,430 -> 770,648
653,136 -> 95,694
645,153 -> 818,153
622,604 -> 77,59
470,597 -> 592,719
753,921 -> 743,921
507,821 -> 507,275
696,969 -> 433,969
48,284 -> 552,284
391,866 -> 187,866
571,897 -> 107,433
24,466 -> 24,186
949,587 -> 582,587
541,32 -> 541,583
12,984 -> 986,10
124,988 -> 124,904
209,548 -> 209,12
828,652 -> 34,652
455,460 -> 905,910
632,638 -> 343,638
371,275 -> 145,275
66,941 -> 945,62
672,286 -> 672,301
447,45 -> 82,410
665,683 -> 487,683
410,582 -> 670,842
917,705 -> 917,323
122,372 -> 403,653
970,47 -> 40,977
15,707 -> 732,707
666,854 -> 666,256
640,230 -> 188,230
71,297 -> 71,472
51,431 -> 339,431
957,363 -> 82,363
844,171 -> 274,171
184,81 -> 184,786
437,456 -> 138,456
326,478 -> 814,966
956,76 -> 226,806
399,572 -> 399,877
923,813 -> 923,898
445,221 -> 947,723
85,590 -> 415,590
644,127 -> 644,140
372,732 -> 372,522
413,186 -> 244,186
291,163 -> 197,69
889,41 -> 208,722
835,925 -> 835,616
790,340 -> 426,340
26,872 -> 805,93
617,701 -> 828,701
344,654 -> 530,654
940,989 -> 41,90
642,87 -> 206,523
306,54 -> 306,761
672,778 -> 672,868
29,959 -> 974,14
458,166 -> 86,166
565,394 -> 352,181
516,805 -> 457,805
464,450 -> 756,450
177,181 -> 661,181
962,394 -> 630,62
94,613 -> 12,613
932,159 -> 818,273
640,642 -> 976,642
184,346 -> 184,22
370,672 -> 458,672
235,334 -> 770,869
207,17 -> 811,17
351,25 -> 896,25
589,178 -> 921,510
672,733 -> 937,733
159,718 -> 787,90
598,317 -> 598,455
591,164 -> 591,374
276,321 -> 515,321
245,431 -> 245,350
268,976 -> 939,305
319,297 -> 823,297
246,132 -> 229,132
338,707 -> 338,59
723,402 -> 723,739
539,846 -> 94,846
879,458 -> 783,458
400,847 -> 770,477
111,957 -> 858,210
640,340 -> 878,578
613,446 -> 115,446
119,480 -> 119,425
447,34 -> 986,34
730,357 -> 532,555
85,283 -> 26,283
834,327 -> 263,898
739,580 -> 640,481
841,938 -> 841,123
987,979 -> 23,15
827,563 -> 632,563
313,419 -> 466,572
323,326 -> 455,326
976,683 -> 976,314
896,568 -> 338,568
733,464 -> 819,464
613,608 -> 493,608
178,137 -> 178,663
262,595 -> 262,308
535,268 -> 851,268
653,111 -> 653,723
947,47 -> 23,971
351,800 -> 351,806
143,49 -> 850,756
872,643 -> 872,265
946,727 -> 427,727
354,284 -> 938,868
652,874 -> 652,447
920,27 -> 387,27
816,265 -> 816,366
238,632 -> 336,534
593,399 -> 593,425
696,108 -> 138,666
828,110 -> 240,698
421,590 -> 847,590
409,449 -> 622,449
986,291 -> 45,291
580,34 -> 663,34
921,988 -> 35,102
619,88 -> 249,88
48,773 -> 503,773
277,560 -> 377,460
139,803 -> 123,787
339,688 -> 907,120
775,117 -> 775,203
237,287 -> 507,557
27,832 -> 429,430
70,592 -> 672,592
701,295 -> 69,295
723,672 -> 714,672
974,359 -> 292,359
439,462 -> 439,973
681,922 -> 357,922
402,521 -> 25,521
297,525 -> 297,227
763,797 -> 763,153
737,634 -> 869,634
379,875 -> 810,875
427,117 -> 705,117
785,376 -> 265,896
922,818 -> 141,37
61,371 -> 61,432
557,303 -> 411,303
573,100 -> 295,100
767,657 -> 231,657
11,938 -> 875,74
782,393 -> 782,502
899,913 -> 519,533
887,11 -> 244,654
730,271 -> 426,271
555,538 -> 379,538
871,866 -> 871,461
934,178 -> 934,845
741,327 -> 325,327
332,394 -> 518,580
673,372 -> 809,236
951,972 -> 77,98
985,585 -> 985,55
604,341 -> 269,676
408,70 -> 455,23
718,239 -> 718,522
15,16 -> 984,985
743,836 -> 852,836
407,59 -> 246,220
388,538 -> 388,587
978,249 -> 810,249
506,945 -> 506,644
657,101 -> 657,522
879,261 -> 935,261
751,625 -> 926,800
676,183 -> 573,286
272,491 -> 765,984
867,207 -> 570,207
117,784 -> 750,784
883,528 -> 883,845
430,765 -> 581,614
748,839 -> 748,775
265,16 -> 188,93
894,584 -> 525,215
278,414 -> 19,673
588,446 -> 588,656
469,676 -> 469,354
383,848 -> 669,848
212,341 -> 367,341
988,582 -> 988,825
296,187 -> 785,187
255,400 -> 255,286
756,886 -> 524,654
966,32 -> 966,940
711,243 -> 81,873
207,34 -> 359,186
555,850 -> 45,850
41,962 -> 947,56
15,888 -> 15,795
773,709 -> 579,515
228,517 -> 228,334
495,290 -> 965,290
462,309 -> 462,421
945,274 -> 945,735
547,361 -> 547,140
37,930 -> 836,930
987,445 -> 45,445
25,766 -> 757,34
643,946 -> 318,621
507,402 -> 507,981
22,903 -> 488,437
887,487 -> 957,487
57,434 -> 21,398
688,907 -> 917,678
146,498 -> 146,965
375,939 -> 888,426
966,151 -> 684,433
160,373 -> 722,373
410,571 -> 410,683
588,667 -> 588,337
832,348 -> 376,804
567,430 -> 453,430
368,442 -> 97,442
11,191 -> 584,764
647,965 -> 647,331
576,856 -> 976,456
205,287 -> 205,495
677,872 -> 677,246
21,16 -> 973,968
949,738 -> 126,738
921,595 -> 921,370
539,821 -> 660,821
143,188 -> 143,793
639,930 -> 639,454
522,360 -> 917,755
128,281 -> 647,800
456,365 -> 456,34
434,530 -> 431,530
549,792 -> 549,892
414,229 -> 41,602
289,468 -> 592,771
555,624 -> 727,452
187,780 -> 187,838
807,282 -> 807,968
52,249 -> 52,332
797,525 -> 462,190
295,562 -> 654,921
862,627 -> 252,627
22,116 -> 866,960
155,652 -> 155,979
598,748 -> 51,748
130,601 -> 231,601
468,183 -> 468,414
208,193 -> 927,912
25,755 -> 770,755
667,755 -> 916,506
395,103 -> 395,468
763,62 -> 763,153
371,816 -> 371,109
508,138 -> 37,609
464,983 -> 707,983
282,295 -> 282,536
646,680 -> 646,775
95,117 -> 95,62
606,963 -> 203,963
403,195 -> 97,195
743,293 -> 743,815
243,748 -> 431,748
953,54 -> 22,985
693,810 -> 656,810
163,221 -> 728,786
187,648 -> 187,337
98,559 -> 98,165
903,49 -> 46,906
600,779 -> 432,779
500,606 -> 500,878
812,797 -> 31,16
204,786 -> 517,786
952,757 -> 952,923
66,809 -> 66,831
479,72 -> 17,72
96,430 -> 96,932
346,667 -> 796,667
731,102 -> 162,102
755,846 -> 755,135
90,18 -> 404,18
828,802 -> 134,108
278,946 -> 278,765
289,163 -> 869,743
134,58 -> 887,58
354,735 -> 904,185
897,74 -> 897,716
508,904 -> 508,719
285,358 -> 795,868
51,155 -> 51,643
579,388 -> 431,240
79,330 -> 869,330
955,863 -> 140,48
800,473 -> 689,473
711,128 -> 918,128
787,166 -> 787,36
257,684 -> 873,68
984,986 -> 10,12
76,580 -> 660,580
265,868 -> 955,868
569,491 -> 466,594
535,654 -> 37,654
889,751 -> 411,273
681,491 -> 600,491
459,721 -> 459,109
824,130 -> 689,265
406,780 -> 406,965
18,31 -> 904,917
179,91 -> 232,38
280,552 -> 428,700
967,980 -> 19,32
47,289 -> 738,980
160,592 -> 120,592
710,412 -> 28,412
40,430 -> 747,430
435,821 -> 747,509
36,741 -> 36,363
787,30 -> 847,30
725,968 -> 725,940
739,356 -> 27,356
65,193 -> 419,193
318,513 -> 404,513
150,230 -> 150,968
847,410 -> 847,505
950,188 -> 950,278
816,312 -> 587,541
980,145 -> 154,145
79,781 -> 616,781
433,284 -> 433,309
624,264 -> 569,264
604,859 -> 570,825
936,691 -> 350,105
259,631 -> 648,242
445,27 -> 445,872
438,689 -> 977,150
718,603 -> 718,327
967,310 -> 98,310
439,381 -> 439,35
645,240 -> 282,240
475,54 -> 475,658
972,610 -> 759,823

Solution for part 1:

lines = split.(split(strip(input), "\n") .|> string, " -> ") .|> x -> split.(x, ",")
lines = [parse.(Int, l[i][j]) for i in 1:2, j in 1:2, l in lines] .+ 1

function map_update!(line)
    if any(line[1,:] .== line[2,:])
        x_range = range(sort(line[:,1])...)
        y_range = range(sort(line[:,2])...)
        vents_map[x_range, y_range] .+= 1
    end
    nothing
end

s = maximum(lines)
vents_map = zeros(s,s)
mapslices(map_update!, lines; dims=[1,2])

sum(vents_map .> 1) |> print

My initial attempt at parsing the input somehow returned a 3-dimensions matrix, then I decided to just went along with it.

Part 2

Show challenge - day 5, part 2

Unfortunately, considering only horizontal and vertical lines doesn’t give you the full picture; you need to also consider diagonal lines.

Because of the limits of the hydrothermal vent mapping system, the lines in your list will only ever be horizontal, vertical, or a diagonal line at exactly 45 degrees. In other words:

  • An entry like 1,1 -> 3,3 covers points 1,1, 2,2, and 3,3.
  • An entry like 9,7 -> 7,9 covers points 9,7, 8,8, and 7,9.

Considering all lines from the above example would now produce the following diagram:

1.1....11.
.111...2..
..2.1.111.
...1.2.2..
.112313211
...1.2....
..1...1...
.1.....1..
1.......1.
222111....

You still need to determine the number of points where at least two lines overlap. In the above example, this is still anywhere in the diagram with a 2 or larger - now a total of 12 points.

Consider all of the lines. At how many points do at least two lines overlap?

For part 2, I only needed to add another if block to check for diagonals:

lines = split.(split(strip(input), "\n") .|> string, " -> ") .|> x -> split.(x, ",")
lines = [parse.(Int, l[i][j]) for i in 1:2, j in 1:2, l in lines] .+ 1

function map_update!(line)
    if any(line[1,:] .== line[2,:])
        x_range = range(sort(line[:,1])...)
        y_range = range(sort(line[:,2])...)
        vents_map[x_range, y_range] .+= 1
    elseif isequal((line[1,:] - line[2,:] .|> abs)...)
        x_range = range(line[1,1], line[2,1]; step=sign(line[2,1] - line[1,1]))
        y_range = range(line[1,2], line[2,2]; step=sign(line[2,2] - line[1,2]))
        for i in 1:length(x_range)
            vents_map[x_range[i], y_range[i]] += 1
        end
    end
    nothing
end

s = maximum(lines)
vents_map = zeros(s,s)
mapslices(map_update!, lines; dims=[1,2])

sum(vents_map .> 1) |> print

Bonus: Animations!

Making an animation is dead simple in Julia, using Plots.jl. You just plug the @animate macro in front of a loop, then export to file with gif()!

using Plots; gr()
using Measures

lines = split.(split(strip(input), "\n") .|> string, " -> ") .|> x -> split.(x, ",")
lines = [parse.(Int, l[i][j]) for i in 1:2, j in 1:2, l in lines] .+ 1

s = maximum(lines)
vents_map = zeros(s,s)

anim = @animate for i in 1:size(lines,3)
    l = lines[:,:,i]
    if any(l[1,:] .== l[2,:])
        x_range = range(sort(l[:,1])...)
        y_range = range(sort(l[:,2])...)
        vents_map[x_range, y_range] .+= 1
    end
    if isequal((l[1,:] - l[2,:] .|> abs)...)
        x_range = range(l[1,1], l[2,1]; step=sign(l[2,1] - l[1,1]))
        y_range = range(l[1,2], l[2,2]; step=sign(l[2,2] - l[1,2]))
        for i in 1:length(x_range)
            vents_map[x_range[i], y_range[i]] += 1
        end
    end
    heatmap(vents_map; legend=:none, framestyle=:none,
            border=:none, clim=(0, 2), c=:binary,
            size=(700,700), padding=(0,0), margin=0mm,
            grid=:none, axis=nothing, ticks=nothing)
end

gif(anim, "./AoC2021/hydrothermal_vents_map.gif", fps=60)

And here the results. The puzzle today basically is to count the number of points where these lines crossed one another:

Let’s do another one, for the surface map this time:

using Plots; gr()
using Measures

lines = split.(split(strip(input), "\n") .|> string, " -> ") .|> x -> split.(x, ",")
lines = [parse.(Int, l[i][j]) for i in 1:2, j in 1:2, l in lines] .+ 1

s = maximum(lines)
vents_map = zeros(s,s)

vent(i, j) = vents_map[i,j]

anim = @animate for i in 1:size(lines,3)
    l = lines[:,:,i]
    if any(l[1,:] .== l[2,:])
        x_range = range(sort(l[:,1])...)
        y_range = range(sort(l[:,2])...)
        vents_map[x_range, y_range] .+= 1
    end
    if isequal((l[1,:] - l[2,:] .|> abs)...)
        x_range = range(l[1,1], l[2,1]; step=sign(l[2,1] - l[1,1]))
        y_range = range(l[1,2], l[2,2]; step=sign(l[2,2] - l[1,2]))
        for i in 1:length(x_range)
            vents_map[x_range[i], y_range[i]] += 1
        end
    end
    surface(1:s, 1:s, vent, legend=:none, zlims=(0,6),
            c=cgrad(:default, rev = true),
            camera=(i/20 + 30, i/10 + 20))
end

gif(anim, "./AoC2021/hydrothermal_vents_map_3d.gif", fps=60)

Which produces this:

Pretty neat, huh?