This past year Team Materialize struck out to do each day of 2023's Advent of Code, an annual programming event with thought-provoking problems that you are encouraged to approach from non-standard directions. We figured we'd try and use SQL for the whole thing.
SQL is a bold choice because it is meant for querying data, and not as much for general computation. Several of the problems call for interesting algorithms, specific data structures, and some flexibility. However, Materialize's core thesis is that you can do so much more with SQL that just query your data. If you want to move operational logic from bespoke code into SQL, you'll need to be able to express that logic. And so, Advent of Code was a great opportunity to stretch our legs, and fingers, and see just how much logic fits into SQL.
Preliminaries
There's a lot of content in the month's problems. There are 49 problems, and although there is some overlap really there is too much to say about all of them. We aren't going to recount each of the problems, the whimsical backstories, and the shape of the problem inputs. We'll try and flag some surprising moments, though, and you should dive into those problems if you are keen (they can each be done on their own).
I (Frank) wrote all of my solutions using Materialize's WITH MUTUALLY RECURSIVE even when recursion was not required. This just helped me start writing, as the blocks allow you to just start naming subqueries and writing SQL.
My solutions all had the same skeletal structure:
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | |
4 | lines(line TEXT) AS ( .. ), |
5 |
|
6 | |
7 | part1(part1 BIGINT) AS ( .. ), |
8 |
|
9 | |
10 | part2(part2 BIGINT) AS ( .. ) |
11 |
|
12 | SELECT * FROM part1, part2; |
13 |
|
As mentioned, we won't always need recursion. However, we often do use recursion, and may even need it. We'll call this out, as the use (and ease) of recursion in SQL was one of the main unlocks.
Week one
Day one was largely about text manipulation, specifically extracting numbers from text, and was well-addressed by using regular expressions to manipulate and search the text.
See the solution
Link to puzzle(s) 🟢 🟢
Part one
The newly-improved calibration document consists of lines of text; each line originally contained a specific calibration value that the Elves now need to recover. On each line, the calibration value can be found by combining the first digit and the last digit (in that order) to form a single two-digit number.
Consider your entire calibration document. What is the sum of all of the calibration values?
1 | SELECT SUM(LEFT(r, 1)::int * 10 + RIGHT(r, 1)::int) AS part1 |
2 | FROM ( |
3 | SELECT regexp_replace(input, '[^\d]', '', 'g') AS r |
4 | FROM aoc_1201 |
5 | ); |
6 |
|
Part two
Your calculation isn't quite right. It looks like some of the digits are actually spelled out with letters: one, two, three, four, five, six, seven, eight, and nine also count as valid "digits".
Equipped with this new information, you now need to find the real first and last digit on each line.
1 | WITH |
2 | lines AS ( |
3 | SELECT regexp_split_to_table(input, '\n') AS line |
4 | FROM aoc_1201 |
5 | ), |
6 | slices AS ( |
7 | SELECT line, index, substring(line, index, width) AS slice |
8 | FROM |
9 | lines, |
10 | generate_series(1, length(line)) AS index, |
11 | generate_series(1, 5) AS width |
12 | ), |
13 | numbers (t, n) AS ( |
14 | VALUES ('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4), ('5', 5), ('6', 6), ('7', 7), ('8', 8), ('9', 9), |
15 | ('zero', 0), ('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5), ('six', 6), ('seven', 7), ('eight', 8), ('nine', 9) |
16 | ), |
17 | findings AS ( |
18 | SELECT line, index, n AS number |
19 | FROM slices, numbers |
20 | WHERE slices.slice = numbers.t |
21 | ), |
22 | first AS ( SELECT DISTINCT ON (line) line, number AS f FROM findings ORDER BY line, index ), |
23 | last AS ( SELECT DISTINCT ON (line) line, number AS l FROM findings ORDER BY line, index DESC ) |
24 | SELECT SUM(f * 10 + l) |
25 | FROM first, last |
26 | WHERE first.line = last.line |
27 |
|
Contributors
Day 1 was brought to you by: @chass, @def-, @doy-materialize, @frankmcsherry, @josharenberg, @morsapaes, @nrainer-materialize
Day two was largely about aggregation: rolling up counts and maxima for games involving numbers of colored cubes; SQL did great here.
See the solution
Link to puzzle(s) 🟢 🟢
Part one
Given a table with the following format:
1 | game_id | set_id | green_cnt | red_cnt | blue_cnt |
2 | ----------+--------+-----------+---------+---------- |
3 | Game 4 | set_2 | 12 | 0 | 0 |
4 | ... |
5 | |
1 | WITH game_cnt AS ( |
2 | SELECT split_part(game_id,' ', 2)::int AS game_id, |
3 | COUNT(set_id) AS total_set_cnt, |
4 | COUNT(set_id) FILTER (WHERE (green_cnt <= 13) AND (red_cnt <= 12) AND (blue_cnt <= 14)) AS possible_set_cnt |
5 | FROM aoc_1202 |
6 | GROUP BY game_id |
7 | ) |
8 | SELECT SUM(game_id) FROM game_cnt WHERE total_set_cnt = possible_set_cnt; |
9 |
|
Part two
1 | WITH game_min AS ( |
2 | SELECT split_part(game_id,' ', 2)::int AS game_id, |
3 | MAX(green_cnt) AS green_min, |
4 | MAX(red_cnt) AS red_min, |
5 | MAX(blue_cnt) AS blue_min |
6 | FROM aoc_1202 |
7 | GROUP BY split_part(game_id,' ', 2)::int |
8 | ) |
9 | SELECT SUM(green_min*red_min*blue_min) FROM game_min; |
10 |
|
Part one + two in one go!
1 | |
2 | with mutually recursive |
3 | |
4 | lines(line TEXT) as (select regexp_split_to_table(input, '\n') as line from input), |
5 | games(game TEXT, report TEXT) as (select regexp_split_to_array(line, ':')[1], regexp_split_to_array(line, ':')[2] from lines), |
6 | round(game TEXT, visible TEXT) as (select game, regexp_split_to_table(report, ';') from games), |
7 | bacon(game TEXT, color TEXT) as (select game, regexp_split_to_table(visible, ',') from round), |
8 | parsed(game INT, color TEXT, number INT) as ( |
9 | select |
10 | substring(game, 5)::INT as game, |
11 | regexp_split_to_array(color, ' ')[3] as color, |
12 | regexp_split_to_array(color, ' ')[2]::INT as number |
13 | from bacon |
14 | ), |
15 | |
16 | limits(color TEXT, number INT) as (SELECT * FROM (VALUES ('red', 12), ('green', 13), ('blue', 14))), |
17 | bad_news(game INT) as ( |
18 | select game |
19 | from parsed, limits |
20 | where parsed.color = limits.color |
21 | AND parsed.number > limits.number |
22 | ), |
23 | plausible(game INT) as (select distinct parsed.game from parsed left join bad_news on(parsed.game = bad_news.game) where bad_news.game IS NULL), |
24 | part1(part1 BIGINT) as (select SUM(game) from plausible), |
25 | |
26 | maximum(game INT, color TEXT, number INT) as (select game, color, max(number) from parsed GROUP BY game, color), |
27 | red(game INT) as (select game from maximum, generate_series(1, number) where color = 'red'), |
28 | blue(game INT) as (select game from maximum, generate_series(1, number) where color = 'blue'), |
29 | green(game INT) as (select game from maximum, generate_series(1, number) where color = 'green'), |
30 | power(game INT, product BIGINT) as (SELECT red.game, count(*) from red, blue, green where red.game = blue.game and blue.game = green.game GROUP BY red.game), |
31 | part2(part2 BIGINT) as (select sum(product)::BIGINT from power) |
32 | select * from part1, part2; |
33 |
|
Contributors
Day 2 was brought to you by: @def-, @frankmcsherry, @morsapaes
Day three has inputs in grid form, where there can be interaction between multiple lines (with symbols above or below others). You are looking for runs of numerals, and I used WMR to track these down; reportedly you can also use regular expressions, but I was not clever enough for that!
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | |
2 | WITH MUTUALLY RECURSIVE |
3 | |
4 | |
5 | lines(line TEXT, row_idx INT) AS ( |
6 | SELECT |
7 | regexp_split_to_array(input, '\n')[row_idx], |
8 | row_idx |
9 | FROM |
10 | input, |
11 | generate_series(1, (SELECT COUNT(*)::INT FROM (SELECT regexp_split_to_table(input, '\n') FROM input))) as row_idx |
12 | ), |
13 | chars(symbol TEXT, row_idx INT, col_idx INT) AS ( |
14 | SELECT |
15 | substring(line, start, 1), |
16 | row_idx, |
17 | start |
18 | FROM |
19 | lines, |
20 | generate_series(1, length(line)) as start |
21 | WHERE |
22 | substring(line, start, 1) != '.' |
23 | ), |
24 | numerals(number TEXT, row_idx INT, col_idx INT) AS ( |
25 | SELECT symbol, row_idx, col_idx |
26 | FROM chars |
27 | WHERE symbol IN ( VALUES ('0'), ('1'), ('2'), ('3'), ('4'), ('5'), ('6'), ('7'), ('8'), ('9') ) |
28 | ), |
29 | symbols(symbol TEXT, row_idx INT, col_idx INT) AS ( |
30 | SELECT symbol, row_idx, col_idx |
31 | FROM chars |
32 | WHERE symbol NOT IN ( VALUES ('0'), ('1'), ('2'), ('3'), ('4'), ('5'), ('6'), ('7'), ('8'), ('9') ) |
33 | ), |
34 | |
35 | |
36 | |
37 | active(number TEXT, row_idx INT, col_idx INT, length INT) AS ( |
38 | |
39 | SELECT numerals.*, 1 |
40 | FROM |
41 | numerals, |
42 | symbols, |
43 | generate_series(-1, 1) row_off, |
44 | generate_series(-1, 1) col_off |
45 | WHERE numerals.row_idx = symbols.row_idx + row_off |
46 | AND numerals.col_idx = symbols.col_idx + col_off |
47 | UNION |
48 | |
49 | SELECT numerals.number || active.number, numerals.row_idx, numerals.col_idx, active.length + 1 |
50 | FROM numerals, active |
51 | WHERE numerals.row_idx = active.row_idx |
52 | AND numerals.col_idx = active.col_idx - 1 |
53 | UNION |
54 | |
55 | SELECT active.number || numerals.number, numerals.row_idx, active.col_idx, active.length + 1 |
56 | FROM numerals, active |
57 | WHERE numerals.row_idx = active.row_idx |
58 | AND numerals.col_idx = active.col_idx + active.length |
59 | ), |
60 | parts(number INT, row_idx INT, col_idx INT, length INT) AS ( |
61 | SELECT active.number::INT, row_idx, col_idx, length |
62 | FROM active |
63 | WHERE (active.row_idx, active.col_idx-1) NOT IN (SELECT row_idx, col_idx FROM numerals) |
64 | AND (active.row_idx, active.col_idx+length) NOT IN (SELECT row_idx, col_idx FROM numerals) |
65 | ), |
66 | part1(part1 BIGINT) AS ( SELECT SUM(parts.number::INT) FROM parts ), |
67 | |
68 | |
69 | |
70 | gear_adjacent(row_idx INT, col_idx INT, number INT, part_row INT, part_col INT) AS ( |
71 | SELECT DISTINCT symbols.row_idx, symbols.col_idx, parts.number, parts.row_idx, parts.col_idx |
72 | FROM |
73 | symbols, |
74 | generate_series(-1, 1) gear_r_off, |
75 | generate_series(-1, 1) gear_c_off, |
76 | parts, |
77 | generate_series(parts.col_idx, parts.col_idx + parts.length - 1) part_col |
78 | WHERE symbols.symbol = '*' |
79 | AND symbols.row_idx + gear_r_off = parts.row_idx |
80 | AND symbols.col_idx + gear_c_off = part_col |
81 | ), |
82 | gears(row_idx INT, col_idx INT) AS ( |
83 | SELECT row_idx, col_idx |
84 | FROM gear_adjacent |
85 | GROUP BY row_idx, col_idx |
86 | HAVING COUNT(*) = 2 |
87 | ), |
88 | gear_products(row_idx INT, col_idx INT, product INT) AS ( |
89 | SELECT DISTINCT gears.row_idx, gears.col_idx, p1.number * p2.number |
90 | FROM gears, gear_adjacent p1, gear_adjacent p2 |
91 | WHERE gears.row_idx = p1.row_idx |
92 | AND gears.col_idx = p1.col_idx |
93 | AND gears.row_idx = p2.row_idx |
94 | AND gears.col_idx = p2.col_idx |
95 | AND (p1.part_row != p2.part_row OR p1.part_col != p2.part_col) |
96 | ), |
97 | part2(part2 BIGINT) AS ( SELECT SUM(product) FROM gear_products) |
98 |
|
99 | SELECT * FROM part1, part2; |
100 |
|
Contributors
Day 3 was brought to you by: @frankmcsherry, @morsapaes
Day four introduced scratch cards where each line of input has some winners and losers. This was easy SQL until part two, in which winners give you other scratch cards, which have winners that give you other scratch cards, which .. you can see the recursion. Despite being wordy and complicated, the SQL isn't so bad:
1 | |
2 | |
3 | expanded(card INT, score BIGINT) AS ( |
4 | SELECT * FROM matches |
5 | UNION ALL |
6 | SELECT |
7 | matches.card, |
8 | matches.score |
9 | FROM |
10 | expanded, |
11 | matches, |
12 | generate_series(1, expanded.score) as step |
13 | WHERE |
14 | expanded.card + step = matches.card |
15 | ), |
16 | part2(part2 BIGINT) AS ( SELECT COUNT(*) FROM expanded) |
17 |
|
This would be tricky to do with non-recursive SQL, as the data itself tells us how to unfold the results. Hooray for recursion!
See the solution
Link to puzzle(s) 🟢 🟢
Part one
1 | WITH parsed AS ( |
2 | SELECT regexp_split_to_table(input, '\n') AS line FROM aoc_1204 |
3 | ), |
4 | numbers AS ( |
5 | SELECT split_part(line,':',1) AS card_id, |
6 | replace(split_part(line,':',2),'|','') AS nrs |
7 | FROM parsed |
8 | ), |
9 | arr AS ( |
10 | SELECT card_id, |
11 | nrs, |
12 | regexp_split_to_array(ltrim(rtrim(nrs)),'\s') AS nrs_arr |
13 | FROM numbers |
14 | ), |
15 | winning AS ( |
16 | SELECT card_id, |
17 | unnest(array_remove(nrs_arr,'')) nr, |
18 | ROW_NUMBER() OVER (PARTITION BY card_id) AS row_num |
19 | FROM arr |
20 | GROUP BY card_id, nr HAVING COUNT(*)>1 |
21 | ORDER BY card_id |
22 | ), |
23 | winning_points AS ( |
24 | SELECT ROUND(EXP(SUM(LN(CASE WHEN row_num = 1 THEN row_num ELSE 2 END)))) AS points |
25 | FROM winning |
26 | GROUP BY card_id |
27 | ) |
28 | SELECT SUM(points) |
29 | FROM winning_points; |
30 |
|
Part two
1 | WITH MUTUALLY RECURSIVE |
2 | lines(line string) AS ( |
3 | SELECT |
4 | regexp_split_to_table(input, '\n') AS line |
5 | FROM |
6 | aoc_1204 |
7 | ), |
8 | cards(match string[]) AS ( |
9 | SELECT |
10 | regexp_match(line, 'Card +(\d+): (.*)') AS match |
11 | FROM |
12 | lines |
13 | ), |
14 | card_parts(card_id int, parts string[]) AS ( |
15 | SELECT |
16 | match[1]::int AS card_id, |
17 | regexp_split_to_array(match[2], ' \| ') AS parts |
18 | FROM |
19 | cards |
20 | ), |
21 | winners(card_id int, val int) AS ( |
22 | SELECT |
23 | card_id, |
24 | regexp_split_to_table(trim(parts[1]), '\s+')::int AS val |
25 | FROM |
26 | card_parts |
27 | ), |
28 | ours(card_id int, val int) AS ( |
29 | SELECT |
30 | card_id, |
31 | regexp_split_to_table(trim(parts[2]), '\s+')::int AS val |
32 | FROM |
33 | card_parts |
34 | ), |
35 | count_winning_numbers(card_id int, count int) AS ( |
36 | SELECT |
37 | ours.card_id, |
38 | count(winners.val)::int AS count |
39 | FROM |
40 | ours LEFT OUTER JOIN winners ON ( |
41 | ours.card_id = winners.card_id AND |
42 | ours.val = winners.val |
43 | ) |
44 | GROUP BY ours.card_id |
45 | ), |
46 | prizes(card_id int, prize_id int) AS ( |
47 | SELECT |
48 | card_id, |
49 | prize_id |
50 | FROM |
51 | count_winning_numbers CROSS JOIN generate_series(card_id + 1, card_id + count) AS prize_id |
52 | UNION |
53 | SELECT |
54 | 0 AS card_id, |
55 | ours.card_id AS prize_id |
56 | FROM |
57 | ours |
58 | ), |
59 | multipliers(card_id int, multiplier int) AS ( |
60 | SELECT |
61 | prizes.prize_id AS card_id, |
62 | SUM(coalesce(multipliers.multiplier, 1))::int AS multiplier |
63 | FROM |
64 | prizes left outer JOIN multipliers ON ( |
65 | prizes.card_id = multipliers.card_id |
66 | ) |
67 | GROUP BY prizes.prize_id |
68 | ) |
69 | SELECT |
70 | SUM(multiplier) AS answer |
71 | FROM |
72 | multipliers; |
73 |
|
Part one + two in one go!
1 | |
2 | WITH MUTUALLY RECURSIVE |
3 | |
4 | |
5 | lines(line TEXT) AS ( |
6 | SELECT regexp_split_to_table(input, '\n') |
7 | FROM input |
8 | ), |
9 | blocks(card TEXT, wins TEXT, have TEXT) AS ( |
10 | SELECT |
11 | TRIM (regexp_split_to_array(line, '(:|\|)')[1]), |
12 | TRIM (regexp_split_to_array(line, '(:|\|)')[2]), |
13 | TRIM (regexp_split_to_array(line, '(:|\|)')[3]) |
14 | FROM |
15 | lines |
16 | ), |
17 | parsed(card INT, wins TEXT[], have TEXT[]) AS ( |
18 | SELECT |
19 | regexp_match(card, '[0-9]+')[1]::INT, |
20 | regexp_split_to_array(wins, ' '), |
21 | regexp_split_to_array(have, ' ') |
22 | FROM blocks |
23 | ), |
24 |
|
25 | |
26 | |
27 | matches(card INT, score BIGINT) AS ( |
28 | SELECT card, ( |
29 | SELECT COUNT(*) |
30 | FROM ( |
31 | SELECT unnest(wins) w |
32 | INTERSECT |
33 | SELECT unnest(have) w |
34 | ) |
35 | WHERE w != '' |
36 | ) |
37 | FROM parsed |
38 | ), |
39 | part1(part1 NUMERIC) AS ( |
40 | SELECT SUM(pow(2, score - 1))::NUMERIC |
41 | FROM matches |
42 | WHERE score > 0 |
43 | ), |
44 |
|
45 | |
46 | |
47 | |
48 | expanded(card INT, score BIGINT) AS ( |
49 | SELECT * FROM matches |
50 | UNION ALL |
51 | SELECT |
52 | matches.card, |
53 | matches.score |
54 | FROM |
55 | expanded, |
56 | matches, |
57 | generate_series(1, expanded.score) as step |
58 | WHERE |
59 | expanded.card + step = matches.card |
60 | ), |
61 | part2(part2 BIGINT) AS ( SELECT COUNT(*) FROM expanded) |
62 |
|
63 | select * from part1, part2; |
64 |
|
Contributors
Day 4 was brought to you by: @chass, @doy-materialize, @frankmcsherry, @morsapaes
Day five was a bit of a bear. It was the same day we were doing a Materialize on-site and we were all a bit distracted, but also it was pretty beefy. You first have to "route" various elements through a sequence of remappings, whose length is defined in the data. You then have to expand that out to routing whole intervals (rather than elements), and .. there is just lots of potential for error. I used recursive SQL to handle all the remapping, but other folks just expanded out their SQL for each of the (ten-ish) remappings.
See the solution
Link to puzzle(s) 🟢 🟢
Part one
1 | WITH seeds AS ( |
2 | SELECT |
3 | regexp_split_to_table( |
4 | regexp_split_to_array( |
5 | regexp_split_to_array(input, '\n')[1], |
6 | ': ' |
7 | )[2], |
8 | ' ' |
9 | )::bigint AS seed |
10 | FROM |
11 | input |
12 | ), |
13 | seed_to_soil_lines AS ( |
14 | SELECT |
15 | regexp_split_to_array( |
16 | regexp_split_to_table( |
17 | regexp_match(input, 'seed-to-soil map:\n([0-9 \n]*?)\n\n')[1], |
18 | '\n' |
19 | ), |
20 | ' ' |
21 | )::bigint[] AS line |
22 | FROM |
23 | input |
24 | ), |
25 | seed_to_soil AS ( |
26 | SELECT |
27 | line[1] AS dst_base, |
28 | line[2] AS src_base, |
29 | line[3] AS len |
30 | FROM |
31 | seed_to_soil_lines |
32 | ), |
33 | soil_to_fertilizer_lines AS ( |
34 | SELECT |
35 | regexp_split_to_array( |
36 | regexp_split_to_table( |
37 | regexp_match(input, 'soil-to-fertilizer map:\n([0-9 \n]*?)\n\n')[1], |
38 | '\n' |
39 | ), |
40 | ' ' |
41 | )::bigint[] AS line |
42 | FROM |
43 | input |
44 | ), |
45 | soil_to_fertilizer AS ( |
46 | SELECT |
47 | line[1] AS dst_base, |
48 | line[2] AS src_base, |
49 | line[3] AS len |
50 | FROM |
51 | soil_to_fertilizer_lines |
52 | ), |
53 | fertilizer_to_water_lines AS ( |
54 | SELECT |
55 | regexp_split_to_array( |
56 | regexp_split_to_table( |
57 | regexp_match(input, 'fertilizer-to-water map:\n([0-9 \n]*?)\n\n')[1], |
58 | '\n' |
59 | ), |
60 | ' ' |
61 | )::bigint[] AS line |
62 | FROM |
63 | input |
64 | ), |
65 | fertilizer_to_water AS ( |
66 | SELECT |
67 | line[1] AS dst_base, |
68 | line[2] AS src_base, |
69 | line[3] AS len |
70 | FROM |
71 | fertilizer_to_water_lines |
72 | ), |
73 | water_to_light_lines AS ( |
74 | SELECT |
75 | regexp_split_to_array( |
76 | regexp_split_to_table( |
77 | regexp_match(input, 'water-to-light map:\n([0-9 \n]*?)\n\n')[1], |
78 | '\n' |
79 | ), |
80 | ' ' |
81 | )::bigint[] AS line |
82 | FROM |
83 | input |
84 | ), |
85 | water_to_light AS ( |
86 | SELECT |
87 | line[1] AS dst_base, |
88 | line[2] AS src_base, |
89 | line[3] AS len |
90 | FROM |
91 | water_to_light_lines |
92 | ), |
93 | light_to_temperature_lines AS ( |
94 | SELECT |
95 | regexp_split_to_array( |
96 | regexp_split_to_table( |
97 | regexp_match(input, 'light-to-temperature map:\n([0-9 \n]*?)\n\n')[1], |
98 | '\n' |
99 | ), |
100 | ' ' |
101 | )::bigint[] AS line |
102 | FROM |
103 | input |
104 | ), |
105 | light_to_temperature AS ( |
106 | SELECT |
107 | line[1] AS dst_base, |
108 | line[2] AS src_base, |
109 | line[3] AS len |
110 | FROM |
111 | light_to_temperature_lines |
112 | ), |
113 | temperature_to_humidity_lines AS ( |
114 | SELECT |
115 | regexp_split_to_array( |
116 | regexp_split_to_table( |
117 | regexp_match(input, 'temperature-to-humidity map:\n([0-9 \n]*?)\n\n')[1], |
118 | '\n' |
119 | ), |
120 | ' ' |
121 | )::bigint[] AS line |
122 | FROM |
123 | input |
124 | ), |
125 | temperature_to_humidity AS ( |
126 | SELECT |
127 | line[1] AS dst_base, |
128 | line[2] AS src_base, |
129 | line[3] AS len |
130 | FROM |
131 | temperature_to_humidity_lines |
132 | ), |
133 | humidity_to_location_lines AS ( |
134 | SELECT |
135 | regexp_split_to_array( |
136 | regexp_split_to_table( |
137 | regexp_match(input, 'humidity-to-location map:\n([0-9 \n]*)')[1], |
138 | '\n' |
139 | ), |
140 | ' ' |
141 | )::bigint[] AS line |
142 | FROM |
143 | input |
144 | ), |
145 | humidity_to_location AS ( |
146 | SELECT |
147 | line[1] AS dst_base, |
148 | line[2] AS src_base, |
149 | line[3] AS len |
150 | FROM |
151 | humidity_to_location_lines |
152 | ), |
153 | soil AS ( |
154 | SELECT |
155 | seed, |
156 | coalesce( |
157 | MAX( |
158 | CASE |
159 | WHEN seed >= src_base AND seed < src_base + len |
160 | THEN dst_base + (seed - src_base) |
161 | ELSE null |
162 | END |
163 | ), |
164 | seed |
165 | ) AS soil |
166 | FROM |
167 | seeds, seed_to_soil |
168 | GROUP BY seed |
169 | ), |
170 | fertilizer AS ( |
171 | SELECT |
172 | soil, |
173 | coalesce( |
174 | MAX( |
175 | CASE |
176 | WHEN soil >= src_base AND soil < src_base + len |
177 | THEN dst_base + (soil - src_base) |
178 | ELSE null |
179 | END |
180 | ), |
181 | soil |
182 | ) AS fertilizer |
183 | FROM |
184 | soil, soil_to_fertilizer |
185 | GROUP BY soil |
186 | ), |
187 | water AS ( |
188 | SELECT |
189 | fertilizer, |
190 | coalesce( |
191 | MAX( |
192 | CASE |
193 | when fertilizer >= src_base AND fertilizer < src_base + len |
194 | then dst_base + (fertilizer - src_base) |
195 | else null |
196 | END |
197 | ), |
198 | fertilizer |
199 | ) AS water |
200 | FROM |
201 | fertilizer, fertilizer_to_water |
202 | GROUP BY fertilizer |
203 | ), |
204 | light AS ( |
205 | SELECT |
206 | water, |
207 | coalesce( |
208 | MAX( |
209 | CASE |
210 | WHEN water >= src_base AND water < src_base + len |
211 | THEN dst_base + (water - src_base) |
212 | ELSE null |
213 | END |
214 | ), |
215 | water |
216 | ) AS light |
217 | FROM |
218 | water, water_to_light |
219 | GROUP BY water |
220 | ), |
221 | temperature AS ( |
222 | SELECT |
223 | light, |
224 | coalesce( |
225 | MAX( |
226 | CASE |
227 | WHEN light >= src_base AND light < src_base + len |
228 | THEN dst_base + (light - src_base) |
229 | ELSE null |
230 | END |
231 | ), |
232 | light |
233 | ) AS temperature |
234 | FROM |
235 | light, light_to_temperature |
236 | GROUP BY light |
237 | ), |
238 | humidity AS ( |
239 | SELECT |
240 | temperature, |
241 | coalesce( |
242 | MAX( |
243 | CASE |
244 | WHEN temperature >= src_base AND temperature < src_base + len |
245 | THEN dst_base + (temperature - src_base) |
246 | ELSE null |
247 | END |
248 | ), |
249 | temperature |
250 | ) AS humidity |
251 | FROM |
252 | temperature, temperature_to_humidity |
253 | GROUP BY temperature |
254 | ), |
255 | location AS ( |
256 | SELECT |
257 | humidity, |
258 | coalesce( |
259 | MAX( |
260 | CASE |
261 | WHEN humidity >= src_base AND humidity < src_base + len |
262 | THEN dst_base + (humidity - src_base) |
263 | ELSE null |
264 | END |
265 | ), |
266 | humidity |
267 | ) AS location |
268 | FROM |
269 | humidity, humidity_to_location |
270 | GROUP BY humidity |
271 | ) |
272 | SELECT |
273 | MIN(location) AS answer |
274 | FROM |
275 | location; |
276 |
|
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 | blocks(head TEXT, body TEXT) AS ( |
3 | SELECT |
4 | split_part(regexp_split_to_table(input, '\n\n'), ':', 1), |
5 | split_part(regexp_split_to_table(input, '\n\n'), ':', 2) |
6 | FROM |
7 | input |
8 | ), |
9 | seeds(seed BIGINT) AS ( |
10 | SELECT regexp_split_to_table(trim(body), ' ')::BIGINT |
11 | FROM blocks |
12 | WHERE head = 'seeds' |
13 | ), |
14 | entry0(src_name TEXT, dst_name TEXT, dst_idx TEXT, src_idx TEXT, len TEXT) AS ( |
15 | SELECT |
16 | split_part(split_part(head, ' ', 1), '-', 1), |
17 | split_part(split_part(head, ' ', 1), '-', 3), |
18 | split_part(regexp_split_to_table(body, '\n'), ' ', 1), |
19 | split_part(regexp_split_to_table(body, '\n'), ' ', 2), |
20 | split_part(regexp_split_to_table(body, '\n'), ' ', 3) |
21 | FROM |
22 | blocks |
23 | WHERE |
24 | head != 'seeds' |
25 | ), |
26 | entry(src_name TEXT, dst_name TEXT, src_idx BIGINT, dst_idx BIGINT, len BIGINT) AS ( |
27 | SELECT |
28 | src_name, |
29 | dst_name, |
30 | src_idx::BIGINT, |
31 | dst_idx::BIGINT, |
32 | len::BIGINT |
33 | FROM |
34 | entry0 |
35 | WHERE |
36 | src_idx != '' |
37 | ), |
38 |
|
39 | |
40 | |
41 | active(name TEXT, idx BIGINT) AS ( |
42 | SELECT 'seed', seed FROM seeds |
43 | UNION ALL |
44 | SELECT |
45 | intent.dst_name, |
46 | COALESCE(intent.idx + (entry.dst_idx - entry.src_idx), idx) |
47 | FROM intent LEFT JOIN entry ON ( |
48 | intent.src_name = entry.src_name AND |
49 | intent.dst_name = entry.dst_name AND |
50 | intent.idx BETWEEN entry.src_idx AND entry.src_idx + len - 1) |
51 | ), |
52 | |
53 | intent(src_name TEXT, dst_name TEXT, idx BIGINT) AS ( |
54 | SELECT DISTINCT entry.src_name, dst_name, idx |
55 | FROM active, entry |
56 | WHERE active.name = entry.src_name |
57 | ), |
58 | part1(part1 BIGINT) AS ( |
59 | SELECT MIN(idx) FROM active WHERE name = 'location' |
60 | ), |
61 |
|
62 | |
63 | |
64 | |
65 | seeds2(start_idx BIGINT, end_idx BIGINT) AS ( |
66 | SELECT |
67 | regexp_split_to_array(trim(body), ' ')[2*x-1]::BIGINT, |
68 | regexp_split_to_array(trim(body), ' ')[2*x-1]::BIGINT + regexp_split_to_array(trim(body), ' ')[2*x]::BIGINT |
69 | FROM |
70 | blocks, |
71 | generate_series(1, array_length(regexp_split_to_array(trim(body), ' '), 1)/2) x |
72 | WHERE head = 'seeds' |
73 | ), |
74 | active2(name TEXT, start_idx BIGINT, end_idx BIGINT) AS ( |
75 | SELECT 'seed', start_idx, end_idx |
76 | FROM seeds2 |
77 | UNION |
78 | SELECT |
79 | dst_name, |
80 | clipped_start + (entry_dst - entry_start), |
81 | clipped_end + (entry_dst - entry_start) |
82 | FROM intersection |
83 | UNION |
84 | SELECT |
85 | name, |
86 | start_idx, |
87 | end_idx |
88 | FROM hole |
89 | ), |
90 | |
91 | intent2(src_name TEXT, dst_name TEXT, start_idx BIGINT, end_idx BIGINT) AS ( |
92 | SELECT DISTINCT entry.src_name, dst_name, start_idx, end_idx |
93 | FROM active2, entry |
94 | WHERE active2.name = entry.src_name |
95 | ), |
96 | |
97 | intersection(src_name TEXT, dst_name TEXT, start_idx BIGINT, end_idx BIGINT, entry_start BIGINT, entry_end BIGINT, clipped_start BIGINT, clipped_end BIGINT, entry_dst BIGINT) AS ( |
98 | SELECT |
99 | intent2.src_name, |
100 | intent2.dst_name, |
101 | intent2.start_idx, |
102 | intent2.end_idx, |
103 | entry.src_idx, |
104 | entry.src_idx + entry.len, |
105 | GREATEST(start_idx, entry.src_idx), |
106 | LEAST(end_idx, entry.src_idx + entry.len), |
107 | entry.dst_idx |
108 | FROM intent2, entry |
109 | WHERE intent2.src_name = entry.src_name |
110 | AND intent2.dst_name = entry.dst_name |
111 | AND GREATEST(intent2.start_idx, entry.src_idx) |
112 | < LEAST(intent2.end_idx, entry.src_idx + entry.len) |
113 | ), |
114 | |
115 | |
116 | hole(name TEXT, start_idx BIGINT, end_idx BIGINT) AS ( |
117 | SELECT * FROM ( |
118 | SELECT |
119 | dst_name, |
120 | clipped_end start_idx, |
121 | ( |
122 | SELECT COALESCE(MIN(i2.clipped_start), i1.end_idx) |
123 | FROM intersection i2 |
124 | WHERE i2.clipped_start >= i1.clipped_end |
125 | AND i2.clipped_start < i1.end_idx |
126 | AND i1.src_name = i2.src_name |
127 | AND i1.dst_name = i2.dst_name |
128 | AND i1.start_idx = i2.start_idx |
129 | AND i1.end_idx = i2.end_idx |
130 | ) end_idx |
131 | FROM intersection i1 |
132 | UNION |
133 | SELECT DISTINCT |
134 | dst_name, |
135 | start_idx, |
136 | ( |
137 | SELECT COALESCE(MIN(i2.clipped_start), i1.end_idx) |
138 | FROM intersection i2 |
139 | WHERE i2.clipped_start >= i1.start_idx |
140 | AND i2.clipped_start < i1.end_idx |
141 | AND i1.src_name = i2.src_name |
142 | AND i1.dst_name = i2.dst_name |
143 | AND i1.start_idx = i2.start_idx |
144 | AND i1.end_idx = i2.end_idx |
145 | ) |
146 | FROM intent2 i1 |
147 | ) |
148 | WHERE start_idx < end_idx |
149 | ), |
150 | part2(part2 BIGINT) AS ( SELECT MIN(start_idx) FROM active2 WHERE name = 'location') |
151 |
|
152 | SELECT * FROM part1, part2; |
153 |
|
Contributors
Day 5 was brought to you by: @doy-materialize, @frankmcsherry, @nrainer-materialize
Day six was about whether you knew (or were willing to learn about) the quadratic formula.
See the solution
Link to puzzle(s) 🟢 🟢
Part one
1 | WITH options AS |
2 | ( |
3 | SELECT |
4 | (floor((time - sqrt(time * time - 4 * record)) / 2) + 1)::int low, |
5 | (ceil((time + sqrt(time * time - 4 * record)) / 2) - 1)::int hi, |
6 | FROM input |
7 | ) |
8 | SELECT exp(sum(ln(hi - low + 1)))::int |
9 | FROM options; |
10 |
|
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | ties(slower NUMERIC, faster NUMERIC) AS ( |
4 | SELECT |
5 | (time + sqrt(time * time - 4 * distance)) / 2 as slower, |
6 | (time - sqrt(time * time - 4 * distance)) / 2 as faster |
7 | FROM input |
8 | ), |
9 | options(choices NUMERIC) AS ( |
10 | SELECT 1 + FLOOR(slower)::NUMERIC - CEIL(faster)::NUMERIC FROM ties |
11 | ), |
12 | part12(part12 NUMERIC) AS ( |
13 | SELECT pow(10.0, SUM(log(choices))) FROM options |
14 | ) |
15 |
|
16 | SELECT * FROM part12; |
17 |
|
Contributors
Day 6 was brought to you by: @doy-materialize, @frankmcsherry, @nrainer-materialize, @petrosagg
Day seven is about scoring poker hands, using some new rules for tie breaking. This was mostly SQL aggregation, as the numbers of each card in each hand largely determine the outcome, other than tie-breaking where I learned about the translate function.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | |
2 | WITH MUTUALLY RECURSIVE |
3 | lines(line TEXT) AS ( SELECT regexp_split_to_table(input, '\n') FROM input ), |
4 | hands(hand TEXT, bid INT) as ( |
5 | SELECT regexp_split_to_array(line, ' ')[1], |
6 | regexp_split_to_array(line, ' ')[2]::INT |
7 | FROM lines |
8 | ), |
9 | cards(hand TEXT, value TEXT, position INT) AS ( |
10 | SELECT hand, substring(hand, pos, 1), pos |
11 | FROM hands, generate_series(1, 5) pos |
12 | ), |
13 |
|
14 | |
15 | counts(hand TEXT, value TEXT, count INT) AS ( |
16 | SELECT hand, value, COUNT(*) |
17 | FROM cards |
18 | GROUP BY hand, value |
19 | ), |
20 | ranked(hand TEXT, bid INT, rank INT, score TEXT) AS ( |
21 | SELECT |
22 | hand, |
23 | bid, |
24 | CASE WHEN hand IN (SELECT hand FROM counts WHERE count = 5) THEN 1 |
25 | WHEN hand IN (SELECT hand FROM counts WHERE count = 4) THEN 2 |
26 | WHEN hand IN (SELECT hand FROM counts WHERE count = 3) |
27 | AND hand IN (SELECT hand FROM counts WHERE count = 2) THEN 3 |
28 | WHEN hand IN (SELECT hand FROM counts WHERE count = 3) THEN 4 |
29 | WHEN hand IN (SELECT hand FROM (SELECT hand FROM counts WHERE count = 2) GROUP BY hand HAVING COUNT(*) = 2) THEN 5 |
30 | WHEN hand IN (SELECT hand FROM counts WHERE count = 2) THEN 6 |
31 | ELSE 7 |
32 | END, |
33 | translate(hand, 'AKQJT98765432', 'ABCDEFGHIJKLM') |
34 | FROM |
35 | hands |
36 | ), |
37 | part1(part1 INT) AS ( |
38 | SELECT SUM(r1.bid) |
39 | FROM ranked r1, ranked r2 |
40 | WHERE r1.rank < r2.rank OR (r1.rank = r2.rank AND r1.score <= r2.score) |
41 | ), |
42 |
|
43 | |
44 | wild(hand TEXT, value TEXT, position INT) AS ( |
45 | SELECT * FROM cards |
46 | UNION |
47 | SELECT c1.hand, c2.value, c1.position |
48 | FROM cards c1, cards c2 |
49 | WHERE c1.hand = c2.hand |
50 | AND c1.value = 'J' |
51 | ), |
52 | wild_hands(hand TEXT, new_hand TEXT) AS ( |
53 | SELECT DISTINCT w1.hand, w1.value || w2.value || w3.value || w4.value || w5.value |
54 | FROM (SELECT * FROM wild w1 WHERE position = 1) w1, |
55 | (SELECT * FROM wild w2 WHERE position = 2) w2, |
56 | (SELECT * FROM wild w3 WHERE position = 3) w3, |
57 | (SELECT * FROM wild w4 WHERE position = 4) w4, |
58 | (SELECT * FROM wild w5 WHERE position = 5) w5 |
59 | WHERE w1.hand = w2.hand |
60 | AND w1.hand = w3.hand |
61 | AND w1.hand = w4.hand |
62 | AND w1.hand = w5.hand |
63 | ), |
64 | wild_cards(hand TEXT, value TEXT, position INT) AS ( |
65 | SELECT DISTINCT new_hand, substring(new_hand, pos, 1), pos |
66 | FROM wild_hands, generate_series(1, 5) pos |
67 | ), |
68 | wild_counts(hand TEXT, value TEXT, count INT) AS ( |
69 | SELECT hand, value, COUNT(*) |
70 | FROM wild_cards |
71 | GROUP BY hand, value |
72 | ), |
73 | wild_ranked(hand TEXT, new_hand TEXT, rank INT, score TEXT) AS ( |
74 | SELECT |
75 | hand, |
76 | new_hand, |
77 | CASE WHEN new_hand IN (SELECT hand FROM wild_counts WHERE count = 5) THEN 1 |
78 | WHEN new_hand IN (SELECT hand FROM wild_counts WHERE count = 4) THEN 2 |
79 | WHEN new_hand IN (SELECT hand FROM wild_counts WHERE count = 3) |
80 | AND new_hand IN (SELECT hand FROM wild_counts WHERE count = 2) THEN 3 |
81 | WHEN new_hand IN (SELECT hand FROM wild_counts WHERE count = 3) THEN 4 |
82 | WHEN new_hand IN (SELECT hand FROM (SELECT hand FROM wild_counts WHERE count = 2) GROUP BY hand HAVING COUNT(*) = 2) THEN 5 |
83 | WHEN new_hand IN (SELECT hand FROM wild_counts WHERE count = 2) THEN 6 |
84 | ELSE 7 |
85 | END, |
86 | translate(hand, 'AKQT98765432J', 'ABCDEFGHIJKLM') |
87 | FROM |
88 | wild_hands |
89 | ), |
90 | best_hands(hand TEXT, new_hand TEXT, rank INT, score TEXT) AS ( |
91 | SELECT DISTINCT ON (hand) hand, new_hand, rank, score |
92 | FROM wild_ranked |
93 | ORDER BY hand, rank, score |
94 | ), |
95 | wild_bids(hand TEXT, bid INT, rank INT, score TEXT) AS ( |
96 | SELECT hands.hand, hands.bid, rank, score |
97 | FROM hands, best_hands |
98 | WHERE hands.hand = best_hands.hand |
99 | ), |
100 | part2(part2 INT) AS ( |
101 | SELECT SUM(r1.bid) |
102 | FROM wild_bids r1, wild_bids r2 |
103 | WHERE r1.rank < r2.rank OR (r1.rank = r2.rank AND r1.score <= r2.score) |
104 | ) |
105 |
|
106 | SELECT * FROM part1, part2; |
107 |
|
Contributors
Day 7 was brought to you by: @frankmcsherry, @nrainer-materialize
Week two
Day eight involved some graph navigation (recursion), and some mathematics. The mathematics were of the form "notice that various things are relatively prime", and it was important to rely on SQL as a tool to support reasoning, as opposed to directly attacking the specified computation. In this case, my problem called for 14,935,034,899,483 steps, and no tool is going to make direct simulation be the right answer.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | route(step TEXT, steps INT) AS ( |
4 | SELECT substring(input, steps, 1), steps |
5 | FROM steps_input, generate_series(1, length(input)) steps |
6 | ), |
7 |
|
8 | |
9 | pos1(state TEXT, steps INT) AS ( |
10 | SELECT 'AAA', 0 |
11 | UNION ALL |
12 | SELECT |
13 | CASE WHEN route.step = 'L' THEN paths.left |
14 | WHEN route.step = 'R' THEN paths.right |
15 | ELSE '???' |
16 | END, |
17 | pos1.steps + 1 |
18 | FROM paths, pos1, route |
19 | WHERE pos1.state = paths.state |
20 | AND 1 + (pos1.steps % 263) = route.steps |
21 | AND pos1.state != 'ZZZ' |
22 | AND pos1.state != '???' |
23 | ) |
24 | part1(part1 INT) AS (SELECT steps FROM pos1 WHERE pos1.state = 'ZZZ'), |
25 |
|
26 | |
27 | pos2(start TEXT, state TEXT, steps INT) AS ( |
28 | SELECT state, state, 0 |
29 | FROM paths |
30 | WHERE substring(state, 3, 1) = 'A' |
31 | UNION ALL |
32 | SELECT |
33 | pos2.start, |
34 | CASE WHEN route.step = 'L' THEN paths.left |
35 | WHEN route.step = 'R' THEN paths.right |
36 | ELSE '???' |
37 | END, |
38 | pos2.steps + 1 |
39 | FROM paths, pos2, route |
40 | WHERE pos2.state = paths.state |
41 | AND 1 + (pos2.steps % 263) = route.steps |
42 | AND substring(pos2.state, 3, 1) != 'Z' |
43 | ) |
44 |
|
45 | SELECT * FROM pos2 WHERE substring(state, 3, 1) = 'Z'; |
46 |
|
Contributors
Day 8 was brought to you by: @doy-materialize, @frankmcsherry, @nrainer-materialize
Day nine was a refreshing introduction to polynomials, and how if you take enough derivatives of them they end up at zero. The task was to do this, repeatedly difference adjacent measurements, or adjacent differences, etc., until you get all zeros. Then, integrate back up to get projections in the forward and reverse direction. I used recursion here to accommodate the unknown degree of the polynomial (somewhere in the twenties).
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE (RETURN AT RECURSION LIMIT 30) |
2 |
|
3 | lines (line TEXT, line_no INT) AS ( |
4 | SELECT regexp_split_to_array(input, '\n')[i], i |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) i |
6 | ), |
7 |
|
8 | numbers(value INT, line_no INT, col_no INT) AS ( |
9 | SELECT regexp_split_to_array(line, ' ')[j]::INT, line_no, j |
10 | FROM lines, generate_series(1, array_length(regexp_split_to_array(line, ' '), 1)) j |
11 | ), |
12 |
|
13 | |
14 | derivatives(value INT, line_no INT, col_no INT, round INT) AS ( |
15 | SELECT numbers.*, 1 |
16 | FROM numbers |
17 | UNION |
18 | SELECT |
19 | COALESCE(i2.value, 0) - COALESCE(i1.value, 0), |
20 | COALESCE(i1.line_no, i2.line_no), |
21 | COALESCE(i1.col_no + 1, i2.col_no), |
22 | COALESCE(i1.round, i2.round) + 1 |
23 | FROM derivatives i1 FULL OUTER JOIN derivatives i2 ON (i1.line_no = i2.line_no AND i1.round = i2.round AND i1.col_no + 1 = i2.col_no) |
24 | WHERE COALESCE(i2.value, 0) - COALESCE(i1.value, 0) != 0 |
25 | AND COALESCE(i1.col_no + 1, i2.col_no) > COALESCE(i1.round, i2.round) |
26 | AND COALESCE(i1.col_no + 1, i2.col_no) <= 21 |
27 | ), |
28 |
|
29 | |
30 | part1(part1 BIGINT) AS ( |
31 | SELECT SUM(value) |
32 | FROM derivatives |
33 | WHERE col_no = 21 |
34 | ), |
35 |
|
36 | |
37 | part2(part2 BIGINT) AS ( |
38 | SELECT SUM(pow(-1, round + 1) * value) |
39 | FROM derivatives |
40 | WHERE col_no = round |
41 | ) |
42 |
|
43 | |
44 | SELECT * FROM part1, part2; |
45 |
|
Contributors
Day 9 was brought to you by: @frankmcsherry, @nrainer-materialize
Day ten presents you with a grid of pipe (symbols |, -, J, 7, F, and L), and questions about how long a loop of pipe is, and then how many cells are contained within it. The first part involved recursion, and I used it again for a dynamic programming solution to the second part.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(line TEXT, row_no INT) AS ( |
4 | SELECT regexp_split_to_array(input, '\n')[i], i |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) i |
6 | ), |
7 |
|
8 | symbols(symb TEXT, row_no INT, col_no INT) as ( |
9 | SELECT substring(line, j, 1), row_no, j |
10 | FROM lines, generate_series(1, length(line)) j |
11 | ), |
12 |
|
13 | |
14 | edge1(r1 INT, c1 INT, r2 INT, c2 INT) AS ( |
15 | SELECT |
16 | row_no, |
17 | col_no, |
18 | CASE WHEN symb = '-' THEN row_no |
19 | WHEN symb = '|' THEN row_no - 1 |
20 | WHEN symb = 'F' THEN row_no + 1 |
21 | WHEN symb = 'L' THEN row_no - 1 |
22 | WHEN symb = 'J' THEN row_no |
23 | WHEN symb = '7' THEN row_no |
24 | ELSE NULL |
25 | END, |
26 | CASE WHEN symb = '-' THEN col_no - 1 |
27 | WHEN symb = '|' THEN col_no |
28 | WHEN symb = 'F' THEN col_no |
29 | WHEN symb = 'L' THEN col_no |
30 | WHEN symb = 'J' THEN col_no - 1 |
31 | WHEN symb = '7' THEN col_no - 1 |
32 | ELSE NULL |
33 | END |
34 | FROM symbols |
35 | WHERE symb != '.' AND symb != 'S' |
36 | ), |
37 | edge2(r1 INT, c1 INT, r2 INT, c2 INT) AS ( |
38 | SELECT |
39 | row_no, |
40 | col_no, |
41 | CASE WHEN symb = '-' THEN row_no |
42 | WHEN symb = '|' THEN row_no + 1 |
43 | WHEN symb = 'F' THEN row_no |
44 | WHEN symb = 'L' THEN row_no |
45 | WHEN symb = 'J' THEN row_no - 1 |
46 | WHEN symb = '7' THEN row_no + 1 |
47 | ELSE NULL |
48 | END, |
49 | CASE WHEN symb = '-' THEN col_no + 1 |
50 | WHEN symb = '|' THEN col_no |
51 | WHEN symb = 'F' THEN col_no + 1 |
52 | WHEN symb = 'L' THEN col_no + 1 |
53 | WHEN symb = 'J' THEN col_no |
54 | WHEN symb = '7' THEN col_no |
55 | ELSE NULL |
56 | END |
57 | FROM symbols |
58 | WHERE symb != '.' AND symb != 'S' |
59 | ), |
60 | |
61 | symm(r1 INT, c1 INT, r2 INT, c2 INT) AS ( |
62 | SELECT r1, c1, r2, c2 |
63 | FROM ( |
64 | SELECT * FROM edge1 |
65 | UNION ALL |
66 | SELECT * FROM edge2 |
67 | UNION ALL |
68 | SELECT r2, c2, r1, c1 FROM edge1 |
69 | UNION ALL |
70 | SELECT r2, c2, r1, c1 FROM edge2 |
71 | UNION ALL |
72 | SELECT row_no, col_no, row_no + 1, col_no FROM symbols WHERE symb = 'S' |
73 | UNION ALL |
74 | SELECT row_no, col_no, row_no, col_no + 1 FROM symbols WHERE symb = 'S' |
75 | UNION ALL |
76 | SELECT row_no, col_no, row_no - 1, col_no FROM symbols WHERE symb = 'S' |
77 | UNION ALL |
78 | SELECT row_no, col_no, row_no, col_no - 1 FROM symbols WHERE symb = 'S' |
79 | ) |
80 | GROUP BY r1, c1, r2, c2 |
81 | HAVING COUNT(*) = 2 |
82 | ), |
83 | reach(r INT, c INT) AS ( |
84 | SELECT row_no, col_no |
85 | FROM symbols |
86 | WHERE symb = 'S' |
87 | UNION |
88 | SELECT r2, c2 |
89 | FROM reach, symm |
90 | WHERE r = r1 AND c = c1 |
91 | ), |
92 | part1(part1 BIGINT) AS ( |
93 | SELECT COUNT(*)/2 FROM reach |
94 | ), |
95 |
|
96 | |
97 | |
98 | |
99 | |
100 | pipe(r INT, c INT, symb TEXT) AS ( |
101 | SELECT r, c, symb |
102 | FROM reach, symbols |
103 | WHERE r = row_no AND c = col_no AND symb != 'S' |
104 | UNION |
105 | SELECT |
106 | row_no, |
107 | col_no, |
108 | CASE WHEN row_no = s1.r1 AND col_no = s1.c1 + 1 AND row_no = s2.r2 + 1 AND col_no = s2.c2 THEN 'J' |
109 | WHEN row_no = s1.r1 AND col_no = s1.c1 + 1 AND row_no = s2.r2 AND col_no = s2.c2 - 1 THEN '-' |
110 | WHEN row_no = s1.r1 AND col_no = s1.c1 + 1 AND row_no = s2.r2 - 1 AND col_no = s2.c2 THEN '7' |
111 | WHEN row_no = s1.r1 + 1 AND col_no = s1.c1 AND row_no = s2.r2 AND col_no = s2.c2 - 1 THEN 'L' |
112 | WHEN row_no = s1.r1 + 1 AND col_no = s1.c1 AND row_no = s2.r2 - 1 AND col_no = s2.c2 THEN '|' |
113 | WHEN row_no = s1.r1 AND col_no = s1.c1 - 1 AND row_no = s2.r2 AND col_no = s2.c2 - 1 THEN 'F' |
114 | ELSE '???' |
115 | END |
116 | FROM symbols, symm s1, symm s2 |
117 | WHERE symb = 'S' |
118 | AND row_no = s1.r1 |
119 | AND col_no = s1.c1 |
120 | AND row_no = s2.r1 |
121 | AND col_no = s2.c1 |
122 | ), |
123 | |
124 | |
125 | status(r INT, c INT, encl BOOL) AS ( |
126 | SELECT row_no, col_no, false |
127 | FROM symbols |
128 | WHERE row_no = 1 OR col_no = 1 |
129 | UNION |
130 | SELECT |
131 | row_no + 1, |
132 | col_no + 1, |
133 | CASE WHEN pipe.symb IN (VALUES ('J'),('-'),('|'),('F')) THEN NOT encl |
134 | ELSE encl |
135 | END |
136 | FROM status LEFT JOIN pipe ON (status.r = pipe.r AND status.c = pipe.c) |
137 | JOIN symbols ON (status.r = symbols.row_no AND status.c = symbols.col_no) |
138 | ), |
139 | part2(part2 BIGINT) AS ( |
140 | SELECT COUNT(*) |
141 | FROM status |
142 | WHERE encl = true AND (r, c) NOT IN (SELECT r, c FROM pipe) |
143 | ) |
144 |
|
145 | SELECT * FROM part1, part2; |
146 |
|
Contributors
Day 10 was brought to you by: @frankmcsherry
Day eleven presents a grid of "galaxies" and has you calculate the distance between pairs (the L1 or "Manhattan" distance, always the sum of absolute values of coordinate differences). Parts one and two were the same, but with different magnitudes of numbers. No recursion here!
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(line TEXT, r INT) AS ( |
4 | SELECT regexp_split_to_array(input, '\n')[i], i |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) i |
6 | ), |
7 |
|
8 | symbols(symb TEXT, r INT, c INT) as ( |
9 | SELECT substring(line, j, 1), r, j |
10 | FROM lines, generate_series(1, length(line)) j |
11 | ), |
12 |
|
13 | row_gaps(r INT) AS ( |
14 | SELECT r |
15 | FROM symbols |
16 | GROUP BY r |
17 | HAVING COUNT(*) FILTER (WHERE symb = '#') = 0 |
18 | ), |
19 |
|
20 | col_gaps(c INT) AS ( |
21 | SELECT c |
22 | FROM symbols |
23 | GROUP BY c |
24 | HAVING COUNT(*) FILTER (WHERE symb = '#') = 0 |
25 | ), |
26 |
|
27 | |
28 | galaxies(r INT, c INT) AS ( |
29 | SELECT |
30 | r + (SELECT COUNT(*) FROM row_gaps WHERE row_gaps.r < symbols.r), |
31 | c + (SELECT COUNT(*) FROM col_gaps WHERE col_gaps.c < symbols.c) |
32 | FROM symbols |
33 | WHERE symb = '#' |
34 | ), |
35 | |
36 | part1(part1 BIGINT) AS ( |
37 | SELECT SUM(ABS(g1.r - g2.r) + ABS(g1.c - g2.c)) |
38 | FROM galaxies g1, galaxies g2 |
39 | WHERE g1.r < g2.r |
40 | OR (g1.r = g2.r AND g1.c < g2.c) |
41 | ) |
42 |
|
43 | |
44 | galaxies2(r INT, c INT) AS ( |
45 | SELECT |
46 | r + 999999 * (SELECT COUNT(*) FROM row_gaps WHERE row_gaps.r < symbols.r), |
47 | c + 999999 * (SELECT COUNT(*) FROM col_gaps WHERE col_gaps.c < symbols.c) |
48 | FROM symbols |
49 | WHERE symb = '#' |
50 | ), |
51 | |
52 | part2(part2 BIGINT) AS ( |
53 | SELECT SUM(ABS(g1.r - g2.r) + ABS(g1.c - g2.c)) |
54 | FROM galaxies2 g1, galaxies2 g2 |
55 | WHERE g1.r < g2.r |
56 | OR (g1.r = g2.r AND g1.c < g2.c) |
57 | ) |
58 |
|
59 | SELECT * FROM part1, part2; |
60 |
|
Contributors
Day 11 was brought to you by: @frankmcsherry, @nrainer-materialize
Day twelve was about sequence alignment, matching partial observations with hard constraints. Dynamic programming was a great solution here, using recursion.
See the solution
Link to puzzle(s) 🟢 🟢
Part one
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, characters TEXT, springs TEXT) AS ( |
4 | SELECT |
5 | row_id, |
6 | regexp_split_to_array(regexp_split_to_array(input, '\n')[row_id], ' ')[1] || '.', |
7 | regexp_split_to_array(regexp_split_to_array(input, '\n')[row_id], ' ')[2] |
8 | FROM |
9 | input, |
10 | generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) row_id |
11 | ), |
12 | characters(r INT, pos INT, symb TEXT) AS ( |
13 | SELECT |
14 | r, |
15 | pos, |
16 | substring(characters, pos, 1) |
17 | FROM |
18 | lines, |
19 | generate_series(1, length(characters)) pos |
20 | ), |
21 | springs(r INT, pos INT, len INT) AS ( |
22 | SELECT |
23 | r, |
24 | pos, |
25 | regexp_split_to_array(springs, ',')[pos]::INT |
26 | FROM |
27 | lines, |
28 | generate_series(1, array_length(regexp_split_to_array(springs, ','), 1)) pos |
29 | ), |
30 |
|
31 | |
32 | |
33 | fits(r INT, chars INT, spring INT) AS ( |
34 | |
35 | SELECT r, 0, 0 |
36 | FROM lines |
37 | |
38 | UNION ALL |
39 | SELECT fits.r, fits.chars + 1, fits.spring |
40 | FROM fits, characters |
41 | WHERE fits.r = characters.r |
42 | AND fits.chars + 1 = characters.pos |
43 | AND characters.symb != '#' |
44 | |
45 | UNION ALL |
46 | SELECT fits.r, fits.chars + springs.len + 1, fits.spring + 1 |
47 | FROM |
48 | fits, |
49 | springs, |
50 | characters |
51 | WHERE fits.r = springs.r |
52 | AND fits.spring + 1 = springs.pos |
53 | AND fits.r = characters.r |
54 | AND fits.chars + springs.len + 1 = characters.pos |
55 | AND characters.symb != '#' |
56 | AND NOT EXISTS (SELECT FROM characters c WHERE c.r = fits.r AND c.symb = '.' AND c.pos BETWEEN fits.chars + 1 AND fits.chars + springs.len) |
57 | ), |
58 |
|
59 | fit_counts(r INT, chars INT, spring INT, count BIGINT) AS ( |
60 | SELECT r, chars, spring, COUNT(*) AS count |
61 | FROM fits |
62 | GROUP BY r, chars, spring |
63 | ), |
64 | counts(r INT, chars INT, spring INT, count BIGINT) AS ( |
65 | SELECT DISTINCT ON (r) r, chars, spring, count |
66 | FROM fit_counts |
67 | ORDER BY r, chars DESC, spring DESC |
68 | ), |
69 |
|
70 | potato (x INT) AS ( SELECT 1 ) |
71 |
|
72 | SELECT SUM(count) FROM counts; |
73 |
|
Contributors
Day 12 was brought to you by: @frankmcsherry, @nrainer-materialize
Day thirteen had grids of observations with the hypothesis that each is mirrored, horizontally or vertically, at some point that you need to find. SQL and subqueries were a great way to validate hypothetical mirroring axes.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | blocks(b INT, block TEXT) AS ( |
4 | SELECT b, regexp_split_to_array(input, '\n\n')[b] as block |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n\n'), 1)) b |
6 | ), |
7 | lines(b INT, r INT, line TEXT) AS ( |
8 | SELECT b, r, regexp_split_to_array(block, '\n')[r] as block |
9 | FROM blocks, generate_series(1, array_length(regexp_split_to_array(block, '\n'), 1)) r |
10 | ), |
11 | cells(b INT, r INT, c INT, symbol TEXT) AS ( |
12 | SELECT b, r, c, substring(line, c, 1) |
13 | FROM lines, generate_series(1, length(line)) c |
14 | ), |
15 | columns(b INT, c INT, column TEXT) AS ( |
16 | SELECT b, c, string_agg(symbol, '' ORDER BY r) FROM cells GROUP BY b, c |
17 | ), |
18 |
|
19 | row_mirror(b INT, r INT) AS ( |
20 | SELECT * |
21 | FROM (SELECT DISTINCT b, r FROM cells) o |
22 | WHERE NOT EXISTS ( |
23 | |
24 | |
25 | SELECT FROM lines |
26 | WHERE o.b = lines.b |
27 | GROUP BY abs(2 * lines.r - (2 * o.r - 1)) |
28 | HAVING COUNT(DISTINCT lines.line) > 1 |
29 | ) |
30 | ), |
31 |
|
32 | col_mirror(b INT, c INT) AS ( |
33 | SELECT * |
34 | FROM (SELECT DISTINCT b, c FROM cells) o |
35 | WHERE NOT EXISTS ( |
36 | |
37 | |
38 | SELECT FROM columns |
39 | WHERE o.b = columns.b |
40 | GROUP BY abs(2 * columns.c - (2 * o.c - 1)) |
41 | HAVING COUNT(DISTINCT columns.column) > 1 |
42 | ) |
43 | ), |
44 |
|
45 | part1(part1 BIGINT) AS ( |
46 | SELECT COALESCE((SELECT SUM(r-1) FROM row_mirror), 0) * 100 |
47 | + COALESCE((SELECT SUM(c-1) FROM col_mirror), 0) |
48 | ), |
49 |
|
50 | row_mirror2(b INT, r INT) AS ( |
51 | SELECT * |
52 | FROM (SELECT DISTINCT b, r FROM cells) o |
53 | WHERE 1 = ( |
54 | SELECT COUNT(*) |
55 | FROM cells c1, cells c2 |
56 | WHERE abs(2 * c1.r - (2 * o.r - 1)) = abs(2 * c2.r - (2 * o.r - 1)) |
57 | AND c1.r < c2.r |
58 | AND c1.c = c2.c |
59 | AND c1.b = c2.b |
60 | AND c1.b = o.b |
61 | AND c1.symbol != c2.symbol |
62 | ) |
63 | ), |
64 |
|
65 | col_mirror2(b INT, c INT) AS ( |
66 | SELECT * |
67 | FROM (SELECT DISTINCT b, c FROM cells) o |
68 | WHERE 1 = ( |
69 | SELECT COUNT(*) |
70 | FROM cells c1, cells c2 |
71 | WHERE abs(2 * c1.c - (2 * o.c - 1)) = abs(2 * c2.c - (2 * o.c - 1)) |
72 | AND c1.c < c2.c |
73 | AND c1.r = c2.r |
74 | AND c1.b = c2.b |
75 | AND c1.b = o.b |
76 | AND c1.symbol != c2.symbol |
77 | ) |
78 | ), |
79 |
|
80 | part2(part2 BIGINT) AS ( |
81 | SELECT COALESCE((SELECT SUM(r-1) FROM row_mirror2), 0) * 100 |
82 | + COALESCE((SELECT SUM(c-1) FROM col_mirror2), 0) |
83 | ), |
84 |
|
85 | potato (x INT) AS ( SELECT 1 ) |
86 |
|
87 | SELECT * FROM part1, part2; |
88 |
|
Contributors
Day 13 was brought to you by: @frankmcsherry, @nrainer-materialize
Day fourteen was a treat, in that it used nested recursion: a WMR block within a WMR block. The problem was simulation of rocks that roll in cardinal directions, changing the direction ninety degrees, and repeating. Each simulation was recursive (rocks roll until they stop), and we were meant to repeat the larger progress a great many times (1,000,000,000 cycles). The only bummer here was the amount of copy/paste re-use, as each of the four cardinal directions had different subqueries.
See the solution
Link to puzzle(s) 🟢 🟢
Part 1
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as block |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 | cells(r INT, c INT, symbol TEXT) AS ( |
8 | SELECT r, c, substring(line, c, 1) |
9 | FROM lines, generate_series(1, length(line)) c |
10 | ), |
11 |
|
12 | northward(r INT, c INT, symbol TEXT) AS ( |
13 | SELECT * FROM northward |
14 | |
15 | UNION ALL SELECT r - 1, c, 'O' FROM north_move |
16 | EXCEPT ALL SELECT r - 1, c, '.' FROM north_move |
17 | UNION ALL SELECT r, c, '.' FROM north_move |
18 | EXCEPT ALL SELECT r, c, 'O' FROM north_move |
19 | |
20 | UNION ALL SELECT * FROM cells |
21 | EXCEPT ALL SELECT * FROM cells_delay |
22 | ), |
23 |
|
24 | |
25 | north_move(r INT, c INT) AS ( |
26 | SELECT n1.r, n1.c |
27 | FROM northward n1, northward n2 |
28 | WHERE n1.symbol = 'O' |
29 | AND n1.r = n2.r + 1 |
30 | AND n1.c = n2.c |
31 | AND n2.symbol = '.' |
32 | ), |
33 |
|
34 | part1(part1 BIGINT) AS ( |
35 | SELECT SUM(1 + (SELECT MAX(r) FROM lines) - r) |
36 | FROM northward |
37 | WHERE symbol = 'O' |
38 | ), |
39 |
|
40 | output (r INT, line TEXT) AS ( |
41 | SELECT r, string_agg(symbol, ' ' ORDER BY c) |
42 | FROM northward |
43 | GROUP BY r |
44 | ), |
45 |
|
46 | cells_delay(r INT, c INT, symbol TEXT) AS ( SELECT * FROM cells ) |
47 |
|
48 | SELECT * FROM part1; |
49 |
|
Part 2
1 | WITH MUTUALLY RECURSIVE (RETURN AT RECURSION LIMIT 142) |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as block |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 | cells(r INT, c INT, symbol TEXT) AS ( |
8 | SELECT r, c, substring(line, c, 1) |
9 | FROM lines, generate_series(1, length(line)) c |
10 | ), |
11 |
|
12 | |
13 | |
14 | round(r INT, c INT, symbol TEXT) AS ( |
15 | SELECT * FROM east |
16 | UNION ALL SELECT * FROM cells |
17 | EXCEPT ALL SELECT * FROM cells_delay |
18 | ), |
19 |
|
20 | north(r INT, c INT, symbol TEXT) AS ( |
21 | WITH MUTUALLY RECURSIVE |
22 | start(r INT, c INT, symbol TEXT) AS ( |
23 | SELECT * FROM round |
24 | ), |
25 | northward(r INT, c INT, symbol TEXT) AS ( |
26 | SELECT * FROM northward |
27 | |
28 | UNION ALL SELECT r - 1, c, 'O' FROM north_move |
29 | EXCEPT ALL SELECT r - 1, c, '.' FROM north_move |
30 | UNION ALL SELECT r, c, '.' FROM north_move |
31 | EXCEPT ALL SELECT r, c, 'O' FROM north_move |
32 | |
33 | UNION ALL SELECT * FROM start |
34 | EXCEPT ALL SELECT * FROM start_delay |
35 | ), |
36 | |
37 | north_move(r INT, c INT) AS ( |
38 | SELECT n1.r, n1.c |
39 | FROM northward n1, northward n2 |
40 | WHERE n1.symbol = 'O' |
41 | AND n1.r = n2.r + 1 |
42 | AND n1.c = n2.c |
43 | AND n2.symbol = '.' |
44 | ), |
45 | start_delay(r INT, c INT, symbol TEXT) AS ( SELECT * FROM start ) |
46 |
|
47 | SELECT * FROM northward |
48 | ), |
49 |
|
50 | west(r INT, c INT, symbol TEXT) AS ( |
51 | WITH MUTUALLY RECURSIVE |
52 | start(r INT, c INT, symbol TEXT) AS ( |
53 | SELECT * FROM north |
54 | ), |
55 | westward(r INT, c INT, symbol TEXT) AS ( |
56 | SELECT * FROM westward |
57 | |
58 | UNION ALL SELECT r, c - 1, 'O' FROM west_move |
59 | EXCEPT ALL SELECT r, c - 1, '.' FROM west_move |
60 | UNION ALL SELECT r, c, '.' FROM west_move |
61 | EXCEPT ALL SELECT r, c, 'O' FROM west_move |
62 | |
63 | UNION ALL SELECT * FROM start |
64 | EXCEPT ALL SELECT * FROM start_delay |
65 | ), |
66 | |
67 | west_move(r INT, c INT) AS ( |
68 | SELECT w1.r, w1.c |
69 | FROM westward w1, westward w2 |
70 | WHERE w1.symbol = 'O' |
71 | AND w1.r = w2.r |
72 | AND w1.c = w2.c + 1 |
73 | AND w2.symbol = '.' |
74 | ), |
75 | start_delay(r INT, c INT, symbol TEXT) AS ( SELECT * FROM start ) |
76 |
|
77 | SELECT * FROM westward |
78 | ), |
79 |
|
80 | south(r INT, c INT, symbol TEXT) AS ( |
81 | WITH MUTUALLY RECURSIVE |
82 | start(r INT, c INT, symbol TEXT) AS ( |
83 | SELECT * FROM west |
84 | ), |
85 | southward(r INT, c INT, symbol TEXT) AS ( |
86 | SELECT * FROM southward |
87 | |
88 | UNION ALL SELECT r + 1, c, 'O' FROM south_move |
89 | EXCEPT ALL SELECT r + 1, c, '.' FROM south_move |
90 | UNION ALL SELECT r, c, '.' FROM south_move |
91 | EXCEPT ALL SELECT r, c, 'O' FROM south_move |
92 | |
93 | UNION ALL SELECT * FROM start |
94 | EXCEPT ALL SELECT * FROM start_delay |
95 | ), |
96 | |
97 | south_move(r INT, c INT) AS ( |
98 | SELECT s1.r, s1.c |
99 | FROM southward s1, southward s2 |
100 | WHERE s1.symbol = 'O' |
101 | AND s1.r = s2.r - 1 |
102 | AND s1.c = s2.c |
103 | AND s2.symbol = '.' |
104 | ), |
105 | start_delay(r INT, c INT, symbol TEXT) AS ( SELECT * FROM start ) |
106 | SELECT * FROM southward |
107 | ), |
108 |
|
109 | east(r INT, c INT, symbol TEXT) AS ( |
110 | WITH MUTUALLY RECURSIVE |
111 | start(r INT, c INT, symbol TEXT) AS ( |
112 | SELECT * FROM south |
113 | ), |
114 | eastward(r INT, c INT, symbol TEXT) AS ( |
115 | SELECT * FROM eastward |
116 | |
117 | UNION ALL SELECT r, c + 1, 'O' FROM east_move |
118 | EXCEPT ALL SELECT r, c + 1, '.' FROM east_move |
119 | UNION ALL SELECT r, c, '.' FROM east_move |
120 | EXCEPT ALL SELECT r, c, 'O' FROM east_move |
121 | |
122 | UNION ALL SELECT * FROM start |
123 | EXCEPT ALL SELECT * FROM start_delay |
124 | ), |
125 | |
126 | east_move(r INT, c INT) AS ( |
127 | SELECT e1.r, e1.c |
128 | FROM eastward e1, eastward e2 |
129 | WHERE e1.symbol = 'O' |
130 | AND e1.r = e2.r |
131 | AND e1.c = e2.c - 1 |
132 | AND e2.symbol = '.' |
133 | ), |
134 | start_delay(r INT, c INT, symbol TEXT) AS ( SELECT * FROM start ) |
135 | SELECT * FROM eastward |
136 | ), |
137 |
|
138 | output (r INT, line TEXT) AS ( |
139 | SELECT r, string_agg(symbol, ' ' ORDER BY c) |
140 | FROM round |
141 | GROUP BY r |
142 | ), |
143 |
|
144 | transitions(source TEXT, target TEXT) AS ( |
145 | SELECT |
146 | (SELECT string_agg(symbol, '' ORDER BY r, c) FROM round), |
147 | (SELECT string_agg(symbol, '' ORDER BY r, c) FROM east) |
148 | UNION ALL |
149 | SELECT * FROM transitions |
150 | ), |
151 |
|
152 | part2(part2 BIGINT) AS ( |
153 | SELECT SUM(1 + (SELECT MAX(r) FROM lines) - r) |
154 | FROM east |
155 | WHERE symbol = 'O' |
156 | ), |
157 |
|
158 | cells_delay(r INT, c INT, symbol TEXT) AS ( SELECT * FROM cells ) |
159 |
|
160 | |
161 | |
162 | |
163 | |
164 | |
165 | |
166 |
|
167 | |
168 |
|
169 | SELECT * FROM part2; |
170 |
|
Contributors
Day 14 was brought to you by: @frankmcsherry
Week three
Day fifteen has you implement a hash function, and then a hash map. Recursion was a handy way to walk through the input to be hashed, though the hash function was simple enough that you could have used math directly instead. The second part (the hash map) did not require recursion, as rather than simulate the operations you could leap to the final state you were looking for.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE (RETURN AT RECURSION LIMIT 10) |
2 |
|
3 | strings(r INT, string TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, ',')[r] |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, ','), 1)) r |
6 | ), |
7 |
|
8 | |
9 | hashes(string TEXT, hash BIGINT) AS ( |
10 | SELECT string, 0 as hash |
11 | FROM strings |
12 | UNION ALL |
13 | SELECT substring(string, 2), ((hash + ascii(substring(string, 1, 1))) * 17) % 256 |
14 | FROM hashes |
15 | WHERE length(string) > 0 |
16 | ), |
17 |
|
18 | part1(part1 BIGINT) AS ( |
19 | SELECT SUM(hash) |
20 | FROM hashes |
21 | WHERE string = '' |
22 | ), |
23 |
|
24 | |
25 | commands(r INT, symb TEXT, op INT) AS ( |
26 | SELECT |
27 | r, |
28 | CASE WHEN substring(string, length(string)) = '-' |
29 | THEN substring(string, 1, length(string)-1) |
30 | ELSE substring(string, 1, length(string)-2) |
31 | END, |
32 | CASE WHEN substring(string, length(string)) = '-' |
33 | THEN 0 |
34 | ELSE substring(string, length(string))::INT |
35 | END |
36 | FROM strings |
37 | ), |
38 | |
39 | |
40 | final_ops(r INT, symb TEXT, op INT) AS ( |
41 | SELECT * |
42 | FROM commands |
43 | WHERE r > COALESCE( |
44 | (SELECT MAX(r) |
45 | FROM commands c2 |
46 | WHERE commands.symb = c2.symb |
47 | AND c2.op = 0), 0) |
48 | ), |
49 | |
50 | final_state(r INT, symb TEXT, op INT) AS ( |
51 | SELECT DISTINCT ON(symb) |
52 | (SELECT MIN(r) FROM final_ops fo2 WHERE fo2.symb = final_ops.symb), |
53 | symb, |
54 | op |
55 | FROM final_ops |
56 | ORDER BY symb, r DESC, op |
57 | ), |
58 | |
59 | hashes2(start TEXT, string TEXT, hash BIGINT) AS ( |
60 | SELECT symb as start, symb as string, 0 as hash |
61 | FROM final_state |
62 | UNION ALL |
63 | SELECT start, substring(string, 2), ((hash + ascii(substring(string, 1, 1))) * 17) % 256 |
64 | FROM hashes2 |
65 | WHERE length(string) > 0 |
66 | ), |
67 | |
68 | binned(hash BIGINT, r INT, symb TEXT, op INT) AS ( |
69 | SELECT hash, final_state.* |
70 | FROM hashes2, final_state |
71 | WHERE hashes2.start = symb |
72 | AND hashes2.string = '' |
73 | ), |
74 | |
75 | part2(part2 BIGINT) AS ( |
76 | SELECT SUM( |
77 | (1 + hash) * |
78 | (SELECT COUNT(*) FROM binned b2 WHERE binned.hash = b2.hash AND binned.r >= b2.r) * |
79 | op |
80 | ) |
81 | FROM binned |
82 | ), |
83 |
|
84 | potato(x int) as (select 1) |
85 |
|
86 | SELECT * FROM part1, part2; |
87 |
|
Contributors
Day 15 was brought to you by: @frankmcsherry, @nrainer-materialize
Day sixteen was about bouncing light around in a grid, and seeing how many grid cells are illuminated. The illumination process was classic recursive SQL, where you keep expanding (row, col, dir) triples until the set reaches a fixed point. In the second part the light sources had an origin, which is just a fourth column to add, tracking the source of each ray of light.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as block |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 | cells(r INT, c INT, symbol TEXT) AS ( |
8 | SELECT r, c, substring(line, c, 1) |
9 | FROM lines, generate_series(1, length(line)) c |
10 | ), |
11 |
|
12 | shift(dir TEXT, symbol TEXT, dr INT, dc INT, new_dir TEXT) AS ( |
13 | VALUES |
14 | ('r', '.', 0, 1, 'r'), |
15 | ('r', '-', 0, 1, 'r'), |
16 | ('r', '|', 1, 0, 'd'), |
17 | ('r', '|', -1, 0, 'u'), |
18 | ('r', '/', -1, 0, 'u'), |
19 | ('r', '\', 1, 0, 'd'), |
20 | ('l', '.', 0, -1, 'l'), |
21 | ('l', '-', 0, -1, 'l'), |
22 | ('l', '|', 1, 0, 'd'), |
23 | ('l', '|', -1, 0, 'u'), |
24 | ('l', '/', 1, 0, 'd'), |
25 | ('l', '\', -1, 0, 'u'), |
26 | ('u', '.', -1, 0, 'u'), |
27 | ('u', '-', 0, 1, 'r'), |
28 | ('u', '-', 0, -1, 'l'), |
29 | ('u', '|', -1, 0, 'u'), |
30 | ('u', '/', 0, 1, 'r'), |
31 | ('u', '\', 0, -1, 'l'), |
32 | ('d', '.', 1, 0, 'd'), |
33 | ('d', '-', 0, 1, 'r'), |
34 | ('d', '-', 0, -1, 'l'), |
35 | ('d', '|', 1, 0, 'd'), |
36 | ('d', '/', 0, -1, 'l'), |
37 | ('d', '\', 0, 1, 'r') |
38 | ), |
39 |
|
40 | |
41 | light(r INT, c INT, dir TEXT) AS ( |
42 | SELECT 1, 1, 'r' |
43 | UNION |
44 | SELECT light.r + dr, light.c + dc, new_dir |
45 | FROM light, cells, shift |
46 | WHERE light.r = cells.r |
47 | AND light.c = cells.c |
48 | AND light.dir = shift.dir |
49 | AND cells.symbol = shift.symbol |
50 | ), |
51 |
|
52 | part1(part1 BIGINT) AS ( |
53 | SELECT COUNT(*) FROM ( |
54 | SELECT DISTINCT light.r, light.c |
55 | FROM light, cells |
56 | WHERE light.r = cells.r |
57 | AND light.c = cells.c |
58 | ) |
59 | ), |
60 |
|
61 | |
62 | light2(r INT, c INT, dir TEXT, source TEXT) AS ( |
63 | SELECT DISTINCT * FROM (SELECT r, (SELECT MIN(c) FROM cells), 'r', 'r' || r FROM cells) UNION |
64 | SELECT DISTINCT * FROM (SELECT r, (SELECT MAX(c) FROM cells), 'l', 'l' || r FROM cells) UNION |
65 | SELECT DISTINCT * FROM (SELECT (SELECT MIN(r) FROM cells), c, 'd', 'd' || c FROM cells) UNION |
66 | SELECT DISTINCT * FROM (SELECT (SELECT MAX(c) FROM cells), c, 'u', 'u' || c FROM cells) UNION |
67 | SELECT light2.r + dr, light2.c + dc, new_dir, source |
68 | FROM light2, cells, shift |
69 | WHERE light2.r = cells.r |
70 | AND light2.c = cells.c |
71 | AND light2.dir = shift.dir |
72 | AND cells.symbol = shift.symbol |
73 | ), |
74 |
|
75 | part2(part2 BIGINT) AS ( |
76 | SELECT MAX(count) FROM ( |
77 | SELECT source, COUNT(*) FROM ( |
78 | SELECT DISTINCT light2.r, light2.c, source |
79 | FROM light2, cells |
80 | WHERE light2.r = cells.r |
81 | AND light2.c = cells.c |
82 | ) |
83 | GROUP BY source |
84 | ) |
85 | ) |
86 |
|
87 | SELECT * FROM part1, part2; |
88 |
|
Contributors
Day 16 was brought to you by: @frankmcsherry
Day seventeen is a pathfinding problem, with constraints on how you move around the path (not too short or too long in any direction at once). Classic recursive SQL to implement Bellman-Ford.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as block |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 | cells(r INT, c INT, cost INT) AS ( |
8 | SELECT r, c, substring(line, c, 1)::INT |
9 | FROM lines, generate_series(1, length(line)) c |
10 | ), |
11 |
|
12 | |
13 | |
14 | |
15 | min_cost(r INT, c INT, dr INT, dc INT, steps INT, cost INT) AS ( |
16 | SELECT r, c, dr, dc, steps, MIN(cost) |
17 | FROM ( |
18 | SELECT 1 as r, 1 as c, 1 as dr, 0 as dc, 0 as steps, 0 as cost |
19 | UNION ALL |
20 | SELECT 1, 1, 0, 1, 0, 0 |
21 | |
22 | UNION ALL |
23 | SELECT cells.r, cells.c, dr, dc, steps + 1, min_cost.cost + cells.cost |
24 | FROM min_cost, cells |
25 | WHERE steps < 3 |
26 | AND cells.r = min_cost.r + dr |
27 | AND cells.c = min_cost.c + dc |
28 | |
29 | UNION ALL |
30 | SELECT cells.r, cells.c, dc, dr, 1, min_cost.cost + cells.cost |
31 | FROM min_cost, cells |
32 | WHERE cells.r = min_cost.r + dc |
33 | AND cells.c = min_cost.c + dr |
34 | |
35 | UNION ALL |
36 | SELECT cells.r, cells.c, -dc, -dr, 1, min_cost.cost + cells.cost |
37 | FROM min_cost, cells |
38 | WHERE cells.r = min_cost.r - dc |
39 | AND cells.c = min_cost.c - dr |
40 | ) |
41 | GROUP BY r, c, dr, dc, steps |
42 | ), |
43 |
|
44 | part1(part1 INT) AS ( |
45 | SELECT MIN(cost) |
46 | FROM min_cost |
47 | WHERE r = (SELECT MAX(r) FROM cells) |
48 | AND c = (SELECT MAX(c) FROM cells) |
49 | ), |
50 |
|
51 | potato(x INT) AS (SELECT 1), |
52 |
|
53 | |
54 | |
55 | |
56 | min_cost2(r INT, c INT, dr INT, dc INT, steps INT, cost INT) AS ( |
57 | SELECT r, c, dr, dc, steps, MIN(cost) |
58 | FROM ( |
59 | SELECT 1 as r, 1 as c, 1 as dr, 0 as dc, 0 as steps, 0 as cost |
60 | UNION ALL |
61 | SELECT 1, 1, 0, 1, 0, 0 |
62 | |
63 | UNION ALL |
64 | SELECT cells.r, cells.c, dr, dc, steps + 1, min_cost2.cost + cells.cost |
65 | FROM min_cost2, cells |
66 | WHERE steps < 10 |
67 | AND cells.r = min_cost2.r + dr |
68 | AND cells.c = min_cost2.c + dc |
69 | |
70 | UNION ALL |
71 | SELECT cells.r, cells.c, dc, dr, 1, min_cost2.cost + cells.cost |
72 | FROM min_cost2, cells |
73 | WHERE steps >= 4 |
74 | AND cells.r = min_cost2.r + dc |
75 | AND cells.c = min_cost2.c + dr |
76 | |
77 | UNION ALL |
78 | SELECT cells.r, cells.c, -dc, -dr, 1, min_cost2.cost + cells.cost |
79 | FROM min_cost2, cells |
80 | WHERE steps >= 4 |
81 | AND cells.r = min_cost2.r - dc |
82 | AND cells.c = min_cost2.c - dr |
83 | ) |
84 | GROUP BY r, c, dr, dc, steps |
85 | ), |
86 | part2(part2 INT) AS ( |
87 | SELECT MIN(cost) |
88 | FROM min_cost2 |
89 | WHERE r = (SELECT MAX(r) FROM cells) |
90 | AND c = (SELECT MAX(c) FROM cells) |
91 | AND steps >= 4 |
92 | ), |
93 |
|
94 | SELECT * FROM part1, part2; |
95 |
|
Contributors
Day 17 was brought to you by: @frankmcsherry
Day eighteen provides instructions of how a digger will move around, excavating earth, and asks you to calculate the area. This is an opportunity to learn about the Trapezoid formula for computing the area as the addition and subtraction of trapezoid areas.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as line |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 |
|
8 | split1(r INT, dr INT, dc INT, steps INT) AS ( |
9 | SELECT |
10 | r, |
11 | CASE WHEN regexp_split_to_array(line, ' ')[1] = 'U' THEN -1 |
12 | WHEN regexp_split_to_array(line, ' ')[1] = 'D' THEN 1 |
13 | ELSE 0 |
14 | END, |
15 | CASE WHEN regexp_split_to_array(line, ' ')[1] = 'L' THEN -1 |
16 | WHEN regexp_split_to_array(line, ' ')[1] = 'R' THEN 1 |
17 | ELSE 0 |
18 | END, |
19 | regexp_split_to_array(line, ' ')[2]::INT |
20 | FROM lines |
21 | ), |
22 |
|
23 | |
24 | |
25 | |
26 | |
27 | path1(r1 INT, c1 INT, r2 INT, c2 INT, rounds INT) AS ( |
28 | SELECT 0, 0, 0, 0, 1 |
29 | UNION |
30 | SELECT |
31 | path1.r2, |
32 | path1.c2, |
33 | path1.r2 + split1.dr * split1.steps, |
34 | path1.c2 + split1.dc * split1.steps, |
35 | path1.rounds + 1 |
36 | FROM path1, split1 |
37 | WHERE path1.rounds = split1.r |
38 | ), |
39 | |
40 | |
41 | |
42 | part1(part1 BIGINT) AS ( |
43 | SELECT |
44 | ABS((SELECT SUM((r1 + r2) * (c1 - c2)) FROM path1)) / 2 |
45 | + (SELECT SUM(steps) FROM split1) / 2 |
46 | + 1 |
47 | ), |
48 |
|
49 | |
50 | split2(r INT, dr INT, dc INT, steps INT) AS ( |
51 | SELECT |
52 | r, |
53 | CASE WHEN substring(regexp_split_to_array(line, ' ')[3], 8, 1) = '3' THEN -1 |
54 | WHEN substring(regexp_split_to_array(line, ' ')[3], 8, 1) = '1' THEN 1 |
55 | ELSE 0 |
56 | END, |
57 | CASE WHEN substring(regexp_split_to_array(line, ' ')[3], 8, 1) = '2' THEN -1 |
58 | WHEN substring(regexp_split_to_array(line, ' ')[3], 8, 1) = '0' THEN 1 |
59 | ELSE 0 |
60 | END, |
61 | 256 * 256 * get_byte(decode('0' || substring(regexp_split_to_array(line, ' ')[3], 3, 5), 'hex'), 0) |
62 | + 256 * get_byte(decode('0' || substring(regexp_split_to_array(line, ' ')[3], 3, 5), 'hex'), 1) |
63 | + get_byte(decode('0' || substring(regexp_split_to_array(line, ' ')[3], 3, 5), 'hex'), 2) |
64 | FROM lines |
65 | ), |
66 |
|
67 | path2(r1 BIGINT, c1 BIGINT, r2 BIGINT, c2 BIGINT, rounds INT) AS ( |
68 | SELECT 0, 0, 0, 0, 1 |
69 | UNION |
70 | SELECT |
71 | path2.r2, |
72 | path2.c2, |
73 | path2.r2 + split2.dr * split2.steps, |
74 | path2.c2 + split2.dc * split2.steps, |
75 | path2.rounds + 1 |
76 | FROM path2, split2 |
77 | WHERE path2.rounds = split2.r |
78 | ), |
79 | |
80 | |
81 | |
82 | part2(part2 BIGINT) AS ( |
83 | SELECT |
84 | ABS((SELECT SUM((r1 + r2) * (c1 - c2)) FROM path2)) / 2 |
85 | + (SELECT SUM(steps) FROM split2) / 2 |
86 | + 1 |
87 | ) |
88 |
|
89 | SELECT * FROM part1, part2; |
90 |
|
Contributors
Day 18 was brought to you by: @frankmcsherry
Day nineteen sneakily introduces you to binary space partitioning, where rules based on inequality tests route you to new rules, until eventually you reach some rule that says "accept" or "reject". This was all pretty easy, except for a substantial amount of SQL overhead related to the various symbols and characters and coordinates all of which required their own columns.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | blocks(block1 TEXT, block2 TEXT) AS ( |
4 | SELECT |
5 | regexp_split_to_array(input, '\n\n')[1] block1, |
6 | regexp_split_to_array(input, '\n\n')[2] block2 |
7 | FROM input |
8 | ), |
9 | states(state TEXT, trans TEXT) AS ( |
10 | SELECT |
11 | regexp_split_to_array(line, '\{')[1] state, |
12 | trim('}' FROM regexp_split_to_array(line, '\{')[2]) trans |
13 | FROM (SELECT regexp_split_to_table(block1, '\n') line FROM blocks) |
14 | ), |
15 | steps(state TEXT, priority INT, rule TEXT) AS ( |
16 | SELECT |
17 | state, |
18 | priority, |
19 | regexp_split_to_array(trans, ',')[priority] |
20 | FROM states, generate_series(1, array_length(regexp_split_to_array(trans, ','), 1)) priority |
21 | ), |
22 |
|
23 | starts(x INT, m INT, a INT, s INT) AS ( |
24 | SELECT |
25 | substring(regexp_split_to_array(trimmed, ',')[1], 3)::INT, |
26 | substring(regexp_split_to_array(trimmed, ',')[2], 3)::INT, |
27 | substring(regexp_split_to_array(trimmed, ',')[3], 3)::INT, |
28 | substring(regexp_split_to_array(trimmed, ',')[4], 3)::INT |
29 | FROM (SELECT trim('\{' FROM trim('\}' FROM regexp_split_to_table(block2, '\n'))) trimmed FROM blocks) |
30 | ), |
31 |
|
32 | |
33 | rules(state TEXT, priority INT, field TEXT, cmp TEXT, val INT, next TEXT) AS ( |
34 | SELECT |
35 | state, |
36 | priority, |
37 | CASE WHEN substring(rule, 2, 1) = '<' OR substring(rule, 2, 1) = '>' |
38 | THEN substring(rule, 1, 1) |
39 | ELSE 'x' |
40 | END, |
41 | CASE WHEN substring(rule, 2, 1) = '<' OR substring(rule, 2, 1) = '>' |
42 | THEN substring(rule, 2, 1) |
43 | ELSE '>' |
44 | END, |
45 | CASE WHEN substring(rule, 2, 1) = '<' OR substring(rule, 2, 1) = '>' |
46 | THEN regexp_split_to_array(substring(rule, 3), ':')[1]::INT |
47 | ELSE '0' |
48 | END, |
49 | CASE WHEN substring(rule, 2, 1) = '<' OR substring(rule, 2, 1) = '>' |
50 | THEN regexp_split_to_array(substring(rule, 3), ':')[2] |
51 | ELSE rule |
52 | END |
53 | FROM steps |
54 | ), |
55 |
|
56 | |
57 | movement(state TEXT, x INT, m INT, a INT, s INT) AS ( |
58 | SELECT 'in' state, * FROM starts |
59 | UNION ALL |
60 | SELECT next, x, m, a, s |
61 | FROM ( |
62 | SELECT DISTINCT ON (state, x, m, a, s) state, x, m, a, s, priority, next |
63 | FROM ( |
64 | SELECT movement.*, rules.next, rules.priority |
65 | FROM movement, rules |
66 | WHERE movement.state = rules.state |
67 | AND CASE WHEN rules.cmp = '<' |
68 | THEN CASE WHEN rules.field = 'x' THEN x < val |
69 | WHEN rules.field = 'm' THEN m < val |
70 | WHEN rules.field = 'a' THEN a < val |
71 | WHEN rules.field = 's' THEN s < val |
72 | ELSE false |
73 | END |
74 | WHEN rules.cmp = '>' |
75 | THEN CASE WHEN rules.field = 'x' THEN x > val |
76 | WHEN rules.field = 'm' THEN m > val |
77 | WHEN rules.field = 'a' THEN a > val |
78 | WHEN rules.field = 's' THEN s > val |
79 | ELSE false |
80 | END |
81 | ELSE false |
82 | END |
83 | ) |
84 | ORDER BY state, x, m, a, s, priority |
85 | ) |
86 | ), |
87 |
|
88 | part1(part1 BIGINT) AS ( |
89 | SELECT SUM(x + m + a + s) |
90 | FROM movement |
91 | WHERE state = 'A' |
92 | ), |
93 |
|
94 | |
95 | region(state TEXT, priority INT, xl INT, xu INT, ml INT, mu INT, al INT, au INT, sl INT, su INT) AS ( |
96 | SELECT 'in', 1, 1, 4000, 1, 4000, 1, 4000, 1, 4000 |
97 | |
98 | UNION ALL |
99 | SELECT |
100 | next, |
101 | 1, |
102 | CASE WHEN rules.field = 'x' AND rules.cmp = '>' THEN GREATEST(val+1, xl) ELSE xl END, |
103 | CASE WHEN rules.field = 'x' AND rules.cmp = '<' THEN LEAST(val-1, xu) ELSE xu END, |
104 | CASE WHEN rules.field = 'm' AND rules.cmp = '>' THEN GREATEST(val+1, ml) ELSE ml END, |
105 | CASE WHEN rules.field = 'm' AND rules.cmp = '<' THEN LEAST(val-1, mu) ELSE mu END, |
106 | CASE WHEN rules.field = 'a' AND rules.cmp = '>' THEN GREATEST(val+1, al) ELSE al END, |
107 | CASE WHEN rules.field = 'a' AND rules.cmp = '<' THEN LEAST(val-1, au) ELSE au END, |
108 | CASE WHEN rules.field = 's' AND rules.cmp = '>' THEN GREATEST(val+1, sl) ELSE sl END, |
109 | CASE WHEN rules.field = 's' AND rules.cmp = '<' THEN LEAST(val-1, su) ELSE su END |
110 | FROM region, rules |
111 | WHERE region.state = rules.state |
112 | AND region.priority = rules.priority |
113 | |
114 | UNION ALL |
115 | SELECT |
116 | region.state, |
117 | region.priority + 1, |
118 | CASE WHEN rules.field = 'x' AND rules.cmp = '<' THEN GREATEST(val, xl) ELSE xl END, |
119 | CASE WHEN rules.field = 'x' AND rules.cmp = '>' THEN LEAST(val, xu) ELSE xu END, |
120 | CASE WHEN rules.field = 'm' AND rules.cmp = '<' THEN GREATEST(val, ml) ELSE ml END, |
121 | CASE WHEN rules.field = 'm' AND rules.cmp = '>' THEN LEAST(val, mu) ELSE mu END, |
122 | CASE WHEN rules.field = 'a' AND rules.cmp = '<' THEN GREATEST(val, al) ELSE al END, |
123 | CASE WHEN rules.field = 'a' AND rules.cmp = '>' THEN LEAST(val, au) ELSE au END, |
124 | CASE WHEN rules.field = 's' AND rules.cmp = '<' THEN GREATEST(val, sl) ELSE sl END, |
125 | CASE WHEN rules.field = 's' AND rules.cmp = '>' THEN LEAST(val, su) ELSE su END |
126 | FROM region, rules |
127 | WHERE region.state = rules.state |
128 | AND region.priority = rules.priority |
129 | ), |
130 |
|
131 | part2(part2 NUMERIC) AS ( |
132 | SELECT SUM((1 + xu - xl)::BIGINT * (1 + mu - ml)::BIGINT * (1 + au - al)::BIGINT * (1 + su - sl)::BIGINT) |
133 | FROM region |
134 | WHERE state = 'A' |
135 | ), |
136 |
|
137 | potato(x INT) AS (SELECT 1) |
138 |
|
139 | SELECT * FROM part1, part2; |
140 |
|
Contributors
Day 19 was brought to you by: @frankmcsherry
Day twenty presents you with the simulation of an asynchronous circuit, and this is the day that almost broke me. Mechanically the SQL isn't that complicated, but debugging the SQL was a real challenge. It got done over the course of a quite long train ride into the evening.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(line TEXT) AS ( SELECT regexp_split_to_table(input, '\n') FROM input ), |
4 | links(name TEXT, link TEXT) AS ( |
5 | SELECT |
6 | substring(regexp_split_to_array(line, ' ')[1], 2), |
7 | trim(',' FROM regexp_split_to_array(line, ' ')[x]) |
8 | FROM |
9 | lines, generate_series(3, array_length(regexp_split_to_array(line, ' '), 1)) x |
10 | ), |
11 | |
12 | types(op TEXT, name TEXT) AS ( |
13 | SELECT |
14 | substring(regexp_split_to_array(line, ' ')[1], 1, 1), |
15 | substring(regexp_split_to_array(line, ' ')[1], 2) |
16 | FROM |
17 | lines |
18 | ), |
19 |
|
20 | |
21 | |
22 | |
23 | |
24 | |
25 |
|
26 | seed(press INT, counter INT) AS ( |
27 | SELECT 1, 1 |
28 | UNION |
29 | SELECT press, counter - 1 |
30 | FROM seed |
31 | WHERE counter > 0 |
32 | UNION |
33 | SELECT press + 1, 20 |
34 | FROM seed |
35 | WHERE counter = 0 |
36 | AND press < 4100 |
37 | ), |
38 |
|
39 | |
40 | pulses(name TEXT, press INT, round INT, pulse TEXT) AS ( |
41 | |
42 | SELECT 'roadcaster', press, 1, 'lo' FROM seed WHERE counter = 0 |
43 | UNION ALL SELECT * FROM flip |
44 | UNION ALL SELECT * FROM conj |
45 | ), |
46 |
|
47 | |
48 | flip(name TEXT, press INT, round INT, pulse TEXT) AS ( |
49 | |
50 | SELECT |
51 | name, |
52 | press, |
53 | round + 1, |
54 | |
55 | CASE WHEN ( |
56 | SELECT COUNT(*) |
57 | FROM signal s1 |
58 | WHERE s1.target = types.name |
59 | AND s1.pulse = 'lo' |
60 | AND ((s1.press < signal.press) OR |
61 | (s1.press = signal.press AND s1.round < signal.round) OR |
62 | (s1.press = signal.press AND s1.round = signal.round AND s1.source < signal.source)) |
63 | ) % 2 = 0 |
64 | THEN 'hi' |
65 | ELSE 'lo' |
66 | END |
67 | FROM signal, types |
68 | WHERE signal.target = types.name |
69 | AND types.op = '%' |
70 | AND signal.pulse = 'lo' |
71 | ), |
72 |
|
73 | |
74 | conj(name TEXT, press INT, round INT, pulse TEXT) AS ( |
75 | SELECT |
76 | name, |
77 | press, |
78 | round + 1, |
79 | |
80 | |
81 | CASE WHEN ( |
82 | (SELECT COUNT(*) FROM links WHERE link = types.name) |
83 | = |
84 | (SELECT COUNT(*) FROM ( |
85 | SELECT DISTINCT ON (source) source, pulse |
86 | FROM signal s1 |
87 | WHERE s1.target = types.name |
88 | AND ((s1.press < signal.press) OR |
89 | (s1.press = signal.press AND s1.round < signal.round) OR |
90 | (s1.press = signal.press AND s1.round = signal.round AND s1.source <= signal.source)) |
91 | OPTIONS (DISTINCT ON INPUT GROUP SIZE = 1000) |
92 | ORDER BY source, press DESC, round DESC |
93 | ) |
94 | WHERE pulse = 'hi')) |
95 | THEN 'lo' |
96 | ELSE 'hi' |
97 | END |
98 | FROM signal, types |
99 | WHERE signal.target = types.name |
100 | AND types.op = '&' |
101 | ), |
102 |
|
103 | |
104 | |
105 | signal(source TEXT, target TEXT, press INT, round INT, pulse TEXT) AS ( |
106 | SELECT pulses.name, links.link, pulses.press, pulses.round, pulses.pulse |
107 | FROM pulses, links |
108 | WHERE pulses.name = links.name |
109 | AND pulses.round > 0 |
110 | ), |
111 |
|
112 | part1(pulse TEXT, count BIGINT) AS ( |
113 | SELECT pulse, count(*) FROM signal GROUP BY pulse |
114 | ), |
115 |
|
116 | potato(x INT) AS (SELECT 1) |
117 |
|
118 | SELECT * FROM signal WHERE target = 'cn' AND pulse = 'hi'; |
119 |
|
Contributors
Day 20 was brought to you by: @frankmcsherry
Day twenty-one was another example of some (recursive) SQL for grid exploration, followed by some mathematics. In this case the grid exploration was standard, determining reachable locations on the grid, and then the math was quadratic extrapolation from a sequence of measurements (to something too large to actually evaluate, an answer of 621,289,922,886,149 reachable states).
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as block |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 | cells(r INT, c INT, symbol TEXT) AS ( |
8 | SELECT r, c, substring(line, c, 1) |
9 | FROM lines, generate_series(1, length(line)) c |
10 | ), |
11 |
|
12 | steps(r INT, c INT) AS ( |
13 | SELECT r, c FROM cells WHERE symbol = 'S' |
14 | EXCEPT ALL |
15 | SELECT * FROM s_delay |
16 | UNION |
17 | SELECT cells.r, cells.c |
18 | FROM cells, ( |
19 | SELECT r + 1, c FROM steps |
20 | UNION SELECT r - 1, c FROM steps |
21 | UNION SELECT r, c + 1 FROM steps |
22 | UNION SELECT r, c - 1 FROM steps |
23 | ) as potato(r,c) |
24 | WHERE cells.r = potato.r |
25 | AND cells.c = potato.c |
26 | AND cells.symbol != '#' |
27 | ), |
28 |
|
29 | s_delay(r INT, c INT) AS ( |
30 | SELECT r, c FROM cells WHERE symbol = 'S' |
31 | ), |
32 |
|
33 | part1(part1 BIGINT) AS ( |
34 | SELECT COUNT(*) FROM (SELECT DISTINCT * FROM steps) |
35 | ), |
36 |
|
37 | |
38 | |
39 | |
40 | |
41 |
|
42 | dists(r INT, c INT, d INT) AS ( |
43 | SELECT r, c, MIN(d) |
44 | FROM ( |
45 | SELECT r, c, 0 d |
46 | FROM cells |
47 | WHERE symbol = 'S' |
48 | UNION ALL |
49 | SELECT potato.r, potato.c, d + 1 |
50 | FROM cells, ( |
51 | SELECT r + 1, c, d FROM dists |
52 | UNION SELECT r - 1, c, d FROM dists |
53 | UNION SELECT r, c + 1, d FROM dists |
54 | UNION SELECT r, c - 1, d FROM dists |
55 | ) as potato(r,c,d) |
56 | WHERE cells.r = 1 + (((potato.r - 1) % 131) + 131) % 131 |
57 | AND cells.c = 1 + (((potato.c - 1) % 131) + 131) % 131 |
58 | AND cells.symbol != '#' |
59 | AND potato.d < 1000 |
60 | ) |
61 | GROUP BY r, c |
62 | ), |
63 |
|
64 | part2(x0 BIGINT, x2 BIGINT, x4 BIGINT, x6 BIGINT) AS ( |
65 | SELECT |
66 | (SELECT COUNT(*) FROM dists WHERE d <= 0 * 131 + 65 AND d % 2 = 1), |
67 | (SELECT COUNT(*) FROM dists WHERE d <= 2 * 131 + 65 AND d % 2 = 1), |
68 | (SELECT COUNT(*) FROM dists WHERE d <= 4 * 131 + 65 AND d % 2 = 1), |
69 | (SELECT COUNT(*) FROM dists WHERE d <= 6 * 131 + 65 AND d % 2 = 1) |
70 | ), |
71 |
|
72 | potato (x INT) AS ( SELECT 1 ) |
73 |
|
74 | SELECT 'idk'; |
75 |
|
Contributors
Day 21 was brought to you by: @frankmcsherry
Week four
The last week was shorter, but also culminated in some pretty exciting problems and techniques.
Day twenty-two had shapes made of cubes falling into a well, and coming to rest on others (or the ground). There were then questions about how many pieces are load bearing, and also for each load bearing piece how many others would fall if they were removed. Dropping the pieces used recursive SQL, determining the load bearing pieces did not, but then scoring the load bearing pieces again required recursion.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as line |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 |
|
8 | cells(r INT, x INT, y INT, z INT) AS ( |
9 | SELECT xs.r, x, y, z |
10 | FROM (SELECT r, generate_series(regexp_split_to_array(regexp_split_to_array(line, '~')[1], ',')[1]::INT, |
11 | regexp_split_to_array(regexp_split_to_array(line, '~')[2], ',')[1]::INT) x FROM lines) xs, |
12 | (SELECT r, generate_series(regexp_split_to_array(regexp_split_to_array(line, '~')[1], ',')[2]::INT, |
13 | regexp_split_to_array(regexp_split_to_array(line, '~')[2], ',')[2]::INT) y FROM lines) ys, |
14 | (SELECT r, generate_series(regexp_split_to_array(regexp_split_to_array(line, '~')[1], ',')[3]::INT, |
15 | regexp_split_to_array(regexp_split_to_array(line, '~')[2], ',')[3]::INT) z FROM lines) zs |
16 | WHERE xs.r = ys.r |
17 | AND xs.r = zs.r |
18 | ), |
19 |
|
20 | |
21 | parts(r INT, x INT, y INT, z INT) AS ( |
22 | SELECT * FROM cells |
23 | EXCEPT ALL SELECT * FROM cells_delayed |
24 | UNION ALL |
25 | SELECT r, x, y, CASE WHEN r IN (SELECT * FROM supported) THEN z ELSE z - 1 END |
26 | FROM parts |
27 | ), |
28 | |
29 | supports(r1 INT, r2 INT) AS ( |
30 | SELECT DISTINCT p1.r, p2.r |
31 | FROM parts p1, parts p2 |
32 | WHERE p1.x = p2.x |
33 | AND p1.y = p2.y |
34 | AND p1.z + 1 = p2.z |
35 | AND p1.r != p2.r |
36 | ), |
37 | supported(r INT) AS ( |
38 | SELECT r FROM parts WHERE z = 1 |
39 | UNION |
40 | SELECT r2 FROM supports |
41 | ), |
42 | |
43 | part1(part1 BIGINT) AS ( |
44 | SELECT COUNT(DISTINCT r) |
45 | FROM lines |
46 | WHERE r NOT IN ( |
47 | SELECT r1 |
48 | FROM supports |
49 | WHERE r2 IN ( |
50 | SELECT r2 |
51 | FROM supports |
52 | GROUP BY r2 |
53 | HAVING COUNT(*) = 1 |
54 | ) |
55 | ) |
56 | ), |
57 |
|
58 | cells_delayed(r INT, x INT, y INT, z INT) AS ( SELECT * FROM cells ), |
59 |
|
60 | |
61 | |
62 | supports_trans(r1 INT, r2 INT) AS ( |
63 | |
64 | SELECT * |
65 | FROM supports |
66 | WHERE r2 IN (SELECT r2 FROM supports GROUP BY r2 HAVING COUNT(*) = 1) |
67 | |
68 | UNION |
69 | SELECT st.r1, s1.r2 |
70 | FROM supports_trans st, supports s1 |
71 | WHERE st.r2 = s1.r1 |
72 | GROUP BY st.r1, s1.r2 |
73 | HAVING COUNT(*) = (SELECT COUNT(*) FROM supports WHERE supports.r2 = s1.r2) |
74 | ), |
75 |
|
76 | part2(part2 BIGINT) AS (SELECT COUNT(*) FROM supports_trans) |
77 |
|
78 | SELECT * FROM part1, part2; |
79 |
|
Contributors
Day 22 was brought to you by: @frankmcsherry
Day twenty-three is a classic example of finding the "longest path" in a directed graph. This is a relatively easy problem when the input is acyclic (part one), and it is NP-hard when the input may have cycles (part two). Part one was a mostly vanilla recursive SQL query, and part two encoded the 32 prior state options in a large integer and just did a lot of work.
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as line |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 |
|
8 | cells(r INT, c INT, symbol TEXT) AS ( |
9 | SELECT r, c, substring(line, c, 1) |
10 | FROM lines, generate_series(1, length(line)) c |
11 | ), |
12 |
|
13 | |
14 | paths(r INT, c INT) AS ( |
15 | SELECT r, c FROM cells WHERE symbol = '.' |
16 | ), |
17 |
|
18 | steps(r1 INT, c1 INT, r2 INT, c2 INT) AS ( |
19 | SELECT r, c, r + 1, c FROM paths WHERE (r + 1, c) IN (SELECT * FROM PATHS) UNION |
20 | SELECT r, c, r - 1, c FROM paths WHERE (r - 1, c) IN (SELECT * FROM PATHS) UNION |
21 | SELECT r, c, r, c + 1 FROM paths WHERE (r, c + 1) IN (SELECT * FROM PATHS) UNION |
22 | SELECT r, c, r, c - 1 FROM paths WHERE (r, c - 1) IN (SELECT * FROM PATHS) |
23 | ), |
24 |
|
25 | |
26 | force(r1 INT, c1 INT, r2 INT, c2 INT) AS ( |
27 | SELECT r-1, c, r+1, c FROM cells WHERE symbol = 'v' UNION ALL |
28 | SELECT r+1, c, r-1, c FROM cells WHERE symbol = '^' UNION ALL |
29 | SELECT r, c-1, r, c+1 FROM cells WHERE symbol = '>' UNION ALL |
30 | SELECT r, c+1, r, c-1 FROM cells WHERE symbol = '<' |
31 | ), |
32 |
|
33 | dists(r INT, c INT, d INT) AS ( |
34 | SELECT 1, 2, 0 |
35 | UNION |
36 | SELECT steps.r2, steps.c2, 1 + MIN(d) |
37 | FROM dists, steps |
38 | WHERE dists.r = steps.r1 |
39 | AND dists.c = steps.c1 |
40 | GROUP BY steps.r2, steps.c2 |
41 | UNION |
42 | SELECT force.r2, force.c2, 2 + MAX(d) |
43 | FROM dists, force |
44 | WHERE dists.r = force.r1 |
45 | AND dists.c = force.c1 |
46 | GROUP BY force.r2, force.c2 |
47 | ), |
48 |
|
49 | |
50 | |
51 | |
52 | paths2(r INT, c INT) AS ( |
53 | SELECT r, c FROM cells WHERE symbol != '#' |
54 | ), |
55 |
|
56 | steps2(r1 INT, c1 INT, r2 INT, c2 INT) AS ( |
57 | SELECT r, c, r + 1, c FROM paths2 WHERE (r + 1, c) IN (SELECT * FROM paths2) UNION |
58 | SELECT r, c, r - 1, c FROM paths2 WHERE (r - 1, c) IN (SELECT * FROM paths2) UNION |
59 | SELECT r, c, r, c + 1 FROM paths2 WHERE (r, c + 1) IN (SELECT * FROM paths2) UNION |
60 | SELECT r, c, r, c - 1 FROM paths2 WHERE (r, c - 1) IN (SELECT * FROM paths2) |
61 | ), |
62 | |
63 | nodes(r INT, c INT) AS ( |
64 | SELECT r1, c1 FROM steps2 GROUP BY r1, c1 HAVING COUNT(*) != 2 |
65 | ), |
66 | |
67 | trail(r1 INT, c1 INT, d INT, r2 INT, c2 INT) AS ( |
68 | SELECT r1, c1, MIN(d), r2, c2 |
69 | FROM ( |
70 | SELECT r1, c1, 1 d, r2, c2 FROM steps2 WHERE (r1, c1) IN (SELECT * FROM nodes) |
71 | UNION ALL |
72 | SELECT trail.r1, trail.c1, d + 1, steps2.r2, steps2.c2 |
73 | FROM trail, steps2 |
74 | WHERE trail.r2 = steps2.r1 |
75 | AND trail.c2 = steps2.c1 |
76 | AND (trail.r1 != steps2.r2 OR trail.c1 != steps2.c2) |
77 | AND (steps2.r1, steps2.c1) NOT IN (SELECT * FROM nodes) |
78 | ) |
79 | GROUP BY r1, c1, r2, c2 |
80 | ), |
81 |
|
82 | links(r1 INT, c1 INT, d INT, r2 INT, c2 INT) AS ( |
83 | SELECT * FROM trail WHERE (r2, c2) IN (SELECT * FROM nodes) |
84 | ), |
85 |
|
86 | |
87 | |
88 | |
89 | |
90 | |
91 | |
92 |
|
93 | |
94 | internal(r INT, c INT, id INT) AS ( |
95 | SELECT r, c, ( |
96 | SELECT COUNT(*) |
97 | FROM nodes n1 |
98 | WHERE (n1.r < n2.r OR (n1.r = n2.r AND n1.c < n2.c)) |
99 | AND (n1.r, n1.c) NOT IN (VALUES (1,2), (12,20), (130,126), (141,140)) |
100 | ) |
101 | FROM nodes n2 |
102 | WHERE (r, c) NOT IN (VALUES (1,2), (12,20), (130,126), (141,140)) |
103 | ), |
104 |
|
105 | longest(r INT, c INT, d INT, v BIGINT) AS ( |
106 | SELECT r, c, MAX(d), v |
107 | FROM ( |
108 | SELECT 12 r, 20 c, 0 d, 0 v |
109 | UNION ALL |
110 | SELECT r2, c2, longest.d + links.d, v + (1::BIGINT << internal.id) |
111 | FROM longest, links, internal |
112 | WHERE longest.r = links.r1 |
113 | AND longest.c = links.c1 |
114 | AND links.r2 = internal.r |
115 | AND links.c2 = internal.c |
116 | AND ((v >> internal.id) % 2) != 1 |
117 | ) |
118 | GROUP BY r, c, v |
119 | ), |
120 |
|
121 | potato(x INT) AS ( SELECT 1 ) |
122 |
|
123 | SELECT * FROM longest ORDER BY d DESC; |
124 |
|
Contributors
Day 23 was brought to you by: @frankmcsherry
Day twenty-four had most folks reach for a numerical solver, something like Mathematica or z3. That is less easy in SQL, and I needed to learn some math instead (specifically how to find the intersection of two line segments). Although part two seemed quite complex, it ended up being relatively easy when you realize a few simplifications (an added dimension that can be ignored until the end, allowing you to re-use part one).
See the solution
Link to puzzle(s) 🟢 🟢
Part one + two in one go!
1 | WITH MUTUALLY RECURSIVE |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as line |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 |
|
8 | observation(r INT, x NUMERIC, y NUMERIC, z NUMERIC, dx NUMERIC, dy NUMERIC, dz NUMERIC) AS ( |
9 | SELECT |
10 | r, |
11 | trim(',' FROM regexp_split_to_array(line, ' ')[1])::NUMERIC, |
12 | trim(',' FROM regexp_split_to_array(line, ' ')[2])::NUMERIC, |
13 | trim(',' FROM regexp_split_to_array(line, ' ')[3])::NUMERIC, |
14 | trim(',' FROM regexp_split_to_array(line, ' ')[5])::NUMERIC, |
15 | trim(',' FROM regexp_split_to_array(line, ' ')[6])::NUMERIC, |
16 | trim(',' FROM regexp_split_to_array(line, ' ')[7])::NUMERIC |
17 | FROM |
18 | lines |
19 | ), |
20 |
|
21 | |
22 | |
23 | meeting(r1 INT, r2 INT, x NUMERIC, y NUMERIC, t1 NUMERIC, t2 NUMERIC) AS ( |
24 | SELECT |
25 | o1.r, |
26 | o2.r, |
27 | o1.x + o1.dx * (((o2.x - o1.x) * o2.dy) - ((o2.y - o1.y) * o2.dx)) / (o1.dx * o2.dy - o1.dy * o2.dx), |
28 | o1.y + o1.dy * (((o2.x - o1.x) * o2.dy) - ((o2.y - o1.y) * o2.dx)) / (o1.dx * o2.dy - o1.dy * o2.dx), |
29 | (((o2.x - o1.x) * o2.dy) - ((o2.y - o1.y) * o2.dx)) / (o1.dx * o2.dy - o1.dy * o2.dx), |
30 | (((o2.x - o1.x) * o1.dy) - ((o2.y - o1.y) * o1.dx)) / (o1.dx * o2.dy - o1.dy * o2.dx) |
31 | FROM observation o1, observation o2 |
32 | WHERE o1.dx * o2.dy != o1.dy * o2.dx |
33 | AND o1.r < o2.r |
34 | ), |
35 | part1(part1 BIGINT) AS ( |
36 | SELECT COUNT(*) |
37 | FROM meeting |
38 | WHERE t1 >= 0 |
39 | AND t2 >= 0 |
40 | AND x BETWEEN 200000000000000 AND 400000000000000 |
41 | AND y BETWEEN 200000000000000 AND 400000000000000 |
42 | ), |
43 |
|
44 | |
45 | |
46 | hypotheses(r INT, x NUMERIC, y NUMERIC, dx NUMERIC, dy NUMERIC, ox NUMERIC, oy NUMERIC) AS ( |
47 | SELECT |
48 | r, x, y, dx - ox, dy - oy, ox, oy |
49 | FROM |
50 | observation, |
51 | generate_series(-500, 500) ox, |
52 | generate_series(-500, 500) oy |
53 | WHERE r < 10 |
54 | AND 5 * (ox + 21) = 16 * (oy + 39) |
55 | ), |
56 | coincidence(r1 INT, r2 INT, x NUMERIC, y NUMERIC, ox NUMERIC, oy NUMERIC) AS ( |
57 | SELECT |
58 | o1.r, |
59 | o2.r, |
60 | o1.x + o1.dx * (((o2.x - o1.x) * o2.dy) - ((o2.y - o1.y) * o2.dx)) / (o1.dx * o2.dy - o1.dy * o2.dx), |
61 | o1.y + o1.dy * (((o2.x - o1.x) * o2.dy) - ((o2.y - o1.y) * o2.dx)) / (o1.dx * o2.dy - o1.dy * o2.dx), |
62 | o1.ox, |
63 | o1.oy |
64 | FROM hypotheses o1, hypotheses o2 |
65 | WHERE o1.dx * o2.dy != o1.dy * o2.dx |
66 | AND o1.r < o2.r |
67 | AND o1.ox = o2.ox |
68 | AND o1.oy = o2.oy |
69 | ), |
70 |
|
71 | hypotheses_xz(r INT, x NUMERIC, y NUMERIC, dx NUMERIC, dy NUMERIC, ox NUMERIC, oy NUMERIC) AS ( |
72 | SELECT |
73 | r, x, z, dx - ox, dz - oz, ox, oz |
74 | FROM |
75 | observation, |
76 | generate_series(-117, -117) ox, |
77 | generate_series(-500, 500) oz |
78 | WHERE r < 10 |
79 | ), |
80 | coincidence_xz(r1 INT, r2 INT, x NUMERIC, y NUMERIC, ox NUMERIC, oy NUMERIC) AS ( |
81 | SELECT |
82 | o1.r, |
83 | o2.r, |
84 | o1.x + o1.dx * (((o2.x - o1.x) * o2.dy) - ((o2.y - o1.y) * o2.dx)) / (o1.dx * o2.dy - o1.dy * o2.dx), |
85 | o1.y + o1.dy * (((o2.x - o1.x) * o2.dy) - ((o2.y - o1.y) * o2.dx)) / (o1.dx * o2.dy - o1.dy * o2.dx), |
86 | o1.ox, |
87 | o1.oy |
88 | FROM hypotheses_xz o1, hypotheses_xz o2 |
89 | WHERE o1.dx * o2.dy != o1.dy * o2.dx |
90 | AND o1.r < o2.r |
91 | AND o1.ox = o2.ox |
92 | AND o1.oy = o2.oy |
93 | ), |
94 |
|
95 | potato (x INT) AS ( SELECT 1 ) |
96 |
|
97 | |
98 | SELECT x, y, ox, oy, COUNT(*) FROM coincidence_xz GROUP BY x, y, ox, oy HAVING COUNT(*) > 1; |
99 |
|
Contributors
Day 24 was brought to you by: @frankmcsherry
Day twenty-five asked for a minimum graph cut (of three edges). This is a standard optimization problem, but rather than try to implement the Stoer-Wagner algorithm I went with something from my PhD thesis: partitioning the graph based on the Fiedler vector. It turns out this gave the right answer on the first try, and the holidays were saved!
See the solution
Link to puzzle(s) 🟢
Part one
1 | WITH MUTUALLY RECURSIVE (RETURN AT RECURSION LIMIT 50) |
2 |
|
3 | lines(r INT, line TEXT) AS ( |
4 | SELECT r, regexp_split_to_array(input, '\n')[r] as line |
5 | FROM input, generate_series(1, array_length(regexp_split_to_array(input, '\n'), 1)) r |
6 | ), |
7 |
|
8 | edges(src TEXT, dst TEXT) AS ( |
9 | SELECT |
10 | trim(':' FROM regexp_split_to_array(line, ' ')[1]), |
11 | trim(',' FROM regexp_split_to_array(line, ' ')[x]) |
12 | FROM |
13 | lines, generate_series(2, array_length(regexp_split_to_array(line, ' '), 1)) x |
14 | ), |
15 |
|
16 | symm(src TEXT, dst TEXT) AS ( |
17 | SELECT src, dst FROM edges |
18 | UNION ALL |
19 | SELECT dst, src FROM edges |
20 | ), |
21 |
|
22 | init(src TEXT, val NUMERIC) AS ( |
23 | SELECT src, CASE WHEN src < 'n' THEN 1.0 ELSE -1.0 END |
24 | FROM (SELECT src FROM edges UNION ALL SELECT dst FROM edges) |
25 | ), |
26 | |
27 | weight(src TEXT, val NUMERIC) AS ( |
28 | SELECT * FROM init |
29 | EXCEPT ALL |
30 | SELECT * FROM init_delayed |
31 | UNION ALL |
32 | SELECT symm.src, SUM((val - (SELECT AVG(val) FROM weight))/(SELECT STDDEV(val) FROM weight)) |
33 | FROM symm, weight |
34 | WHERE symm.dst = weight.src |
35 | GROUP BY symm.src |
36 | ), |
37 |
|
38 | init_delayed(src TEXT, val NUMERIC) AS ( SELECT * FROM init ), |
39 |
|
40 | part1(part1 BIGINT) AS ( |
41 | SELECT |
42 | (SELECT COUNT(*) FROM weight WHERE val < 0.0) * |
43 | (SELECT COUNT(*) FROM weight WHERE val > 0.0) |
44 | ), |
45 |
|
46 | potato(x INT) AS ( SELECT 1 ) |
47 |
|
48 | SELECT * FROM part1; |
49 |
|
Contributors
Day 25 was brought to you by: @frankmcsherry
Conclusions
The exercise was certainly helpful and informative, on multiple levels.
First, it really reinforced for me that WITH MUTUALLY RECURSIVE is a very valuable tool to have access to when faced with a new problem. Often your problem is a bunch of joins and reductions, but when it isn't you are immediately in a bit of a pickle. In most cases, algorithmic challenges immediately gave way to recursive SQL.
That being said, there's clearly an accessibility gap when reaching for recursive SQL. I find the idioms approachable, but I've spent a while working with data-parallel algorithms, and have seen several of the tricks. There's still plenty of work to do before the casual SQL author feels comfortable with recursive SQL.
Second, the majority of my time was spent debugging rather than authoring. This is a classic challenge with declaritive languages, who go from input program to output data in often inscrutable ways. I borrowed some techniques from debugging Datalog, but ideally the system itself would help me with this (and several research systems do provide integrated lineage).
Debugging the logic of SQL queries only gets harder when the data are changing underneath you. Techniques like spot checking data become infeasible when the data changes faster than you can observe records that are meant to line up. Materialize should help in these cases, with maintained diagnostic views that represent assertions, or better violations thereof, whose contents spell out records that at some moment violated something that was meant to be true. Materialize's SUBSCRIBE provides a full account of these views, reporting records that existed even for a moment, where anything other than "always empty" represents an error in your SQL (or your assertions).
Third, using Materialize in new and weird ways shook out several bugs. We've already fixed them. Dogfooding your own product, especially in surprising contexts, is a great way to forcibly increase your test coverage. Issues ranged from the silly ("why would you name a table count?") to the abstruse (doubly nested recursive SQL blocks), but they spilled out in the early days and became less frequent as the weeks went on.
Finally, the main conclusion was that it was all possible. Despite substantial anxiety about whether and when we would need to bail out, defeated, the whole project did work out. We were able to express a rich variety of computational tasks as data-driven SQL both expressed and maintained by Materialize.