AdventOfCode 2025 Day 5

Part One

根据数字范围,判断下面的数字是否在数字范围内,难度不大

with origin AS (
    SELECT row_number() over () as rn, line
    FROM lance_input
), empty AS (
    SELECT rn
    FROM origin
    WHERE line is null
), range AS (
    SELECT split_part(line, '-', 1) :: BIGINT as min_num, split_part(line, '-', 2) :: BIGINT as max_num
    FROM origin, empty
    WHERE origin.rn < empty.rn
)
SELECT count(distinct case when origin.line :: BIGINT between range.min_num and range.max_num then origin.line end)
FROM origin, empty, range
WHERE origin.rn > empty.rn;

Part Two

判断所有的数字范围中,包含的数字个数。

generate_series

可以直接generate_series来生成min和max之间的个数,然后去重即可。

with origin AS (
    SELECT row_number() over () as rn, line
    FROM lance_input
), empty AS (
    SELECT rn
    FROM origin
    WHERE line is null
), range AS (
    SELECT split_part(line, '-', 1) :: BIGINT as min_num, split_part(line, '-', 2) :: BIGINT as max_num
    FROM origin, empty
    WHERE origin.rn < empty.rn
)
SELECT COUNT(distinct num)
FROM (
    SELECT generate_series(min_num, max_num) as num
    FROM range
) t;

但是例子中给的数字太大了,运行很久都没有得出结果。

merge_range

数字范围中有很多重合的,可以找出这些重合的范围进行合并

    larger_range AS (
        SELECT distinct least(a.min_num, b.min_num) as min_num, greatest(a.max_num, b.max_num) as max_num
        FROM inner_tbl a
        JOIN inner_tbl b
        ON a.min_num <= b.max_num
        AND a.max_num >= b.max_num
        AND a.rn != b.rn
    )

之前的数字范围,需要过滤掉被合并的小范围

SELECT step, min_num, max_num
FROM larger_range
UNION ALL
SELECT step, min_num, max_num
FROM (
    SELECT s.step, s.min_num, s.max_num
    FROM inner_tbl s
    LEFT JOIN larger_range t
    ON s.min_num >= t.min_num and s.max_num <= t.max_num
    WHERE t.min_num is null
) s

最后的终止条件,都是再也找不到可以合并的数字范围即可。完整的SQL如下

with recursive origin AS (
    SELECT row_number() over () as rn, line
    FROM lance_input
), empty AS (
    SELECT rn
    FROM origin
    WHERE line is null
), range AS (
    SELECT row_number() over () as rn, split_part(line, '-', 1) :: BIGINT as min_num, split_part(line, '-', 2) :: BIGINT as max_num
    FROM origin, empty
    WHERE origin.rn < empty.rn
), merge_range AS (
    SELECT 1 as step, rn, min_num, max_num
    FROM range

    UNION ALL

    SELECT step + 1 as step, row_number() over () as rn, min_num, max_num
    FROM (
        WITH inner_tbl AS (select * from merge_range),
        larger_range AS (
            SELECT distinct a.step, least(a.min_num, b.min_num) as min_num, greatest(a.max_num, b.max_num) as max_num
            FROM inner_tbl a
            JOIN inner_tbl b
            ON a.min_num <= b.max_num
            AND a.max_num >= b.max_num
            AND a.rn != b.rn
        )
        SELECT step, min_num, max_num
        FROM (
            SELECT step, min_num, max_num
            FROM larger_range
            UNION ALL
            SELECT step, min_num, max_num
            FROM (
                SELECT s.step, s.min_num, s.max_num
                FROM inner_tbl s
                LEFT JOIN larger_range t
                ON s.min_num >= t.min_num and s.max_num <= t.max_num
                WHERE t.min_num is null
            ) s
        ) t
        WHERE exists (
            SELECT 1 FROM inner_tbl a
            JOIN inner_tbl b
            ON a.min_num <= b.max_num
            AND a.max_num >= b.max_num
            AND a.rn != b.rn
        )
    ) t
)
select * from merge_range;

发表评论