暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

用纯 SQL 硬刚 Advent of Code?ClickHouse 把「不可能」变成了 12 天的现实

ClickHouseInc 2026-01-16
48


本文字数:39754;估计阅读时间:100 分钟

作者:Zach Naimon


介绍

每年十二月,编程社区都会迎来一场集体仪式:Advent of Codehttps://adventofcode.com/。这个活动由 Eric Wastl 创建,是一个由小型编程谜题组成的降临节日历,从 12 月 1 日开始到 12 日结束,每天都会发布一个全新的挑战。

这些任务并不是常见的“修复一个 bug”或“构建一个 API”。它们是偏算法方向的挑战,涉及复杂的数据结构、图遍历、三维几何、元胞自动机模拟以及路径查找算法。因此,开发者通常会选择 Python、Rust、Go 或 C++ 等通用编程语言来完成这些题目。


为什么不是 SQL?

让数据库来解决这类问题,通常被认为是一个错误的选择。标准 SQL 是一种声明式语言,主要用于关系型数据的检索与聚合,而并非为命令式的游戏循环或复杂的状态管理而设计。它缺乏这些谜题常用的数据结构支持(例如栈、队列或树),而试图通过标准的 JOIN 操作来实现,往往会引发严重的性能问题,甚至直接导致语法错误。简而言之,用 SQL 来完成 Advent of Code 被广泛视为“不可行”的——至少也是极其痛苦的。


ClickHouse 的方法

在 ClickHouse,我们并不喜欢“不可行”这个词。我们坚信,只要工具合适,一切问题本质上都是数据问题。ClickHouse 不只是一个高速的 OLAP 数据库,它还是一个向量化查询引擎,内置了大量分析函数库,这些能力经过巧妙组合后,甚至可以用来解决通用计算问题。

为了验证这一点,我们决定用一种非常规的方式来完成 2025 年的 Advent of Code:完全使用 ClickHouse SQL。


规则 

为了确保整个过程不存在任何取巧行为,我们为解决方案制定了三条严格的规则:

  1. 仅使用纯 ClickHouse SQL:我们完全不允许使用任何用户自定义函数(User Defined Functions,UDFs),尤其是可执行的 UDF,因为那样可以通过调用 Python 或 Bash 来“作弊”。如果查询引擎本身无法原生完成某件事,我们就不能使用任何变通手段。

  2. 只接受原始输入:在 Advent of Code 中,输入通常是杂乱的文本文件,有时是数字列表,有时是 ASCII 艺术地图,或者是一段晦涩的指令文本。我们不允许对输入进行任何预处理。解决方案查询必须直接接收 AoC 提供的原始输入字符串,并在查询内部完成解析。

  3. “单一查询”约束:这是最具挑战性的一条规则。我们不能创建表、物化视图或临时表来保存中间状态。整个谜题流程——从输入解析,到解决第 1 部分,再到解决通常复杂得多的第 2 部分——都必须在一个单一、原子的查询中完成。这迫使我们大量依赖 CTE,在一次连续的执行过程中将所有逻辑串联起来。

下面展示的是 Advent of Code 2025 全部 12 天的解决方案,展示了我们如何将被认为“不可行”的算法挑战,转化为纯粹的 ClickHouse SQL 查询。

注意:为了遵守 Advent of Code 的分发政策,下面的查询通过一个封装的 URL() 表来获取原始谜题输入,而不会直接暴露输入内容。支持直接字符串输入的原始查询版本,可以在我们的 ClickHouse/TreeHouse 仓库中找到。



第 1 天:秘密入口

谜题:精灵们用一个带有旋转拨盘的保险装置锁住了他们的秘密入口。这个谜题需要根据一系列指令来模拟一个标记为 0–99 的拨盘的移动,例如 L68(向左转动 68 个刻度)或 R48(向右转动 48 个刻度)。

  • 第 1 部分要求在初始位置为 50 的情况下,计算所有旋转指令执行完毕后拨盘的最终位置。

  • 第 2 部分则需要进行更复杂的模拟:精确统计在整个过程中拨盘指向 0 的次数,包括在旋转过程中多次经过 0 的所有中间刻度。

我们是如何用 ClickHouse SQL 解决这个问题的:我们没有将其实现为过程式的循环,而是把整个过程视为一个流处理问题。由于拨盘的状态完全由历史移动决定,我们可以一次性计算出每一条指令对应的累计位置。具体做法是将方向解析为正数(向右)和负数(向左)的整数,然后使用窗口函数计算步数的累计和。在第 2 部分中,为了检测是否“经过 0”,我们比较当前累计值与上一行的累计值,从而判断拨盘是否跨过了 0。


实现细节:

1. sum() OVER (...): 我们使用标准的 SQL 窗口函数来维护拨盘位置的“累计值”。通过将左右方向统一归一化为正值和负值,我们可以在一次扫描中得到每一行对应的累计位置。

    sum(normalized_steps) OVER (ORDER BY instruction_id) AS raw_position

    2. lagInFrame:为了统计经过 0 的次数,我们需要知道当前旋转开始前拨盘的位置。我们使用 lagInFrame 查看上一行的累计位置,从而比较一次旋转的起点和终点,并通过数学方式判断 0 是否位于两者之间。


    完整解决方案:

      WITH
      --Fetch puzzle input
      input_wrapper AS (SELECT raw_blob AS input FROM aoc.input1),


      -- Parse the input string into individual instructions
      parsed_instructions AS (
          -- Initial placeholder row
          SELECT
              0 AS instruction_id,
              'R50' AS raw_instruction,
              'R' AS direction,
              50::Int16 AS steps


          UNION ALL


          -- Parse each line from input
          SELECT
              rowNumberInAllBlocks() + 1 AS instruction_id,
              raw AS raw_instruction,
              substring(raw, 11AS direction,
              substring(raw, 2)::Int16 AS steps
          FROM format(TSV, 'raw String', (SELECT input FROM input_wrapper))
      ), 


      -- Part 1: Calculate positions with simple modulo wrapping
      part1_positions AS (
          SELECT
              instruction_id,
              raw_instruction,
              direction,
              steps,


              -- Normalize direction: positive for R, negative for L
              if(direction = 'R', steps % 100-1 * (steps % 100)) AS normalized_steps,


              -- Calculate cumulative position
              sum(normalized_steps) OVER (
                  ORDER BY instruction_id
              ) AS raw_position,


              -- Wrap position to 0-99 range
              ((raw_position % 100+ 100% 100 AS position
          FROM parsed_instructions
      ),


      -- Part 2: Calculate positions with full movement tracking
      position_calculations AS (
          SELECT
              instruction_id,
              raw_instruction,
              direction,
              steps,


              -- Normalize direction (no modulo yet)
              if(direction = 'R', steps, -1 * steps) AS normalized_steps,


              -- Calculate cumulative raw position
              sum(normalized_steps) OVER (
                  ORDER BY instruction_id ASC
              ) AS raw_position,


              -- Wrap to 0-99 range
              ((raw_position % 100+ 100% 100 AS position
          FROM parsed_instructions
      ),


      -- Track turn counts based on position changes
      turn_tracking AS (
          SELECT
              *,


              -- Get previous position for comparison
              lagInFrame(position) OVER (
                  ORDER BY instruction_id ASC 
                  ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
              ) AS previous_position,


              -- Calculate turns for this instruction
              if(
                  instruction_id = 0,
                  0,


                  -- Base turns from full rotations
                  floor(steps / 100+ 


                  -- Additional turn if we wrapped around
                  if(
                      direction = 'R',
                      (position != 0 AND previous_position != 0 AND position < previous_position) ? 1 : 0,
                      (position != 0 AND previous_position != 0 AND position > previous_position) ? 1 : 0
                  )
              ) + 


              -- Extra turn if we land exactly on position 0
              if(instruction_id != 0 AND position = 010AS turn_count
          FROM position_calculations
      ),


      -- Calculate cumulative turn counts
      part2_turn_counts AS (
          SELECT
              instruction_id,
              raw_instruction,
              direction,
              steps,
              position,
              turn_count,


              -- Running sum of turns
              sum(turn_count) OVER (
                  ORDER BY instruction_id ASC 
                  ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
              ) AS cumulative_turns
          FROM turn_tracking
      )


      -- Final results for both parts
      SELECT 
          'Part 1' AS part,
          countIf(position = 0AS solution -- Should be 1100 with my input
      FROM part1_positions


      UNION ALL


      SELECT 
          'Part 2' AS part,
          max(cumulative_turns)::UInt64 AS solution -- Should be 6358 with my input
      FROM part2_turn_counts;

      查看完整谜题描述:https://adventofcode.com/2025/day/1


      第 2 天:礼品店

      谜题:你正在帮助清理一个礼品店数据库,其中充满了无效的产品 ID。输入数据是一系列 ID 范围(例如 11-22、95-115)。

      • 第 1 部分将无效 ID 定义为由某个序列恰好重复两次组成的数字(例如 1212 或 55)。

      • 第 2 部分将这一规则扩展为任意序列至少重复两次(例如 123123123 或 11111)。目标是在给定的范围内找出所有无效 ID,并对它们求和。

      我们是如何用 ClickHouse SQL 解决这个问题的:我们没有编写循环逐个遍历数字,而是利用了 ClickHouse 将数据“展开”为多行的能力。我们把紧凑的输入范围(例如 11-22)直接扩展成大量独立的行——范围内的每一个整数都会生成一行数据。在为每一个潜在 ID 创建行之后,我们将其转换为字符串,并并行地使用数组函数来检测是否存在重复模式。


      实现细节:

      1. arrayJoin:这是我们生成行时最常用的函数。我们使用 range(start, end) 为每一行输入生成一个整数数组,再通过 arrayJoin 将数组展开为独立的多行数据。这样一来,筛选无效 ID 就只需要一个简单的 WHERE 条件。

        SELECT arrayJoin(range(bounds[1], bounds[2] + 1)) AS number

        2. arrayExists:在第 2 部分中,我们需要检查任意子串长度(从 1 到字符串长度)是否构成了重复模式。为此,我们使用带有 lambda 函数的 arrayExists 来遍历所有可能的子串长度。如果对任意长度返回 1,该 ID 就会被判定为无效。

          arrayExists(
              x -> (string_length % x = 0AND (repeat(substring(..., x), ...) = number_string),
              range(1, string_length)
          )


          完整解决方案:


            -- Define puzzle input
            WITH input_wrapper AS (SELECT trimRight(raw_blob,'\n'AS input FROM aoc.input2),


            -- Parse range bounds from input string
            range_bounds AS (
                SELECT arrayMap(
                    x -> x::UInt64,
                    splitByChar('-', arrayJoin(splitByChar(',', (SELECT input FROM input_wrapper)::String)))
                ) AS bounds
            ),


            -- Expand ranges into individual numbers
            expanded_numbers AS (
                SELECT
                    arrayJoin(range(bounds[1], bounds[2+ 1)) AS number,
                    toString(number) AS number_string,
                    length(number_string) AS string_length
                FROM range_bounds
            ),


            -- Analyze each number for repeating patterns
            repeating_analysis AS (
                SELECT
                    number_string,
                    toUInt64(number_string) AS number,


                    -- Part 2: Check if string is made of any repeating pattern
                    -- (e.g., "123123" = "123" repeated, "1111" = "1" repeated)
                    arrayExists(
                        x -> (string_length % x = 0)
                            AND (
                                repeat(
                                    substring(number_string, 1, x),
                                    (string_length / x)::UInt32
                                ) = number_string
                            ),
                        range(1, string_length)
                    ) AS has_pattern_repeat,


                    -- Part 1: Check if second half equals first half
                    -- (e.g., "1212" -> "12" = "12", "123123" -> "123" = "123")
                    if(
                        string_length % 2 = 0 
                        AND substring(number_string, (string_length / 2+ 1, string_length / 2
                            = substring(number_string, 1, string_length / 2),
                        1,
                        0
                    ) AS has_half_repeat
                FROM expanded_numbers
                WHERE
                    has_pattern_repeat != 0 
                    OR has_half_repeat != 0
                ORDER BY number ASC
            )


            -- Calculate final solutions
            SELECT 
                sumIf(number, has_half_repeat = 1AS part_1_solution, -- Should be 24043483400 with my input
                sumIf(number, has_pattern_repeat = 1AS part_2_solution -- Should be 38262920235 with my input
            FROM repeating_analysis

            查看完整谜题描述:https://adventofcode.com/2025/day/2


            第 3 天:大厅

            谜题:你需要使用多组电池来应急启动一部自动扶梯,每一组电池都表示为一个数字字符串(例如 987654321)。

            • 第 1 部分要求你恰好选取两块电池(数字),在保持它们原始相对顺序不变的前提下,组成能够得到的最大两位数。

            • 第 2 部分将这一问题扩展到更大的规模:需要恰好选取 12 块电池,组成最大的 12 位数。这就变成了一个典型的贪心优化问题——在每一步中,你都希望选择当前能选到的最大数字,同时又必须保证在它之后还剩下足够的数字,用来完成整个序列。

            我们是如何用 ClickHouse SQL 解决这个问题的:第 1 部分只涉及简单的字符串操作,而第 2 部分则要求在遍历数字时持续维护状态。我们需要同时跟踪还剩多少个数字需要选择,以及当前在字符串中的位置,从而避免打乱原有顺序。为此,我们直接在 SQL 中使用 arrayFold 实现了这一贪心算法,它允许我们在遍历数组的同时,携带一个包含约束条件的累加器元组。


            实现细节:

            1. arrayFold:我们使用这个高阶函数来实现类似 reduce() 的逻辑。累加器中保存了一个元组:(digits_remaining, current_position, accumulated_value)。在 fold 的每一步中,我们都会计算当前可以选择的最优合法数字,并据此更新整个状态元组。

              arrayFold(
                  (accumulator, current_element) -> ( ... ), -- Update logic
                  digits,
                  (num_digits_needed, 00-- Initial state
              )

              2. ngrams:为了把数字字符串当作数组来处理,我们使用了 ngrams(string, 1)。这个函数通常用于文本分析,但在这里,它恰好可以把字符串拆分成由单个字符组成的数


              完整解决方案:


                -- Define puzzle input
                WITH input_wrapper AS (SELECT trimBoth(raw_blob,'\n'AS input FROM aoc.input3),


                -- Convert input to array of digit values for Part 2
                digit_array AS (
                    SELECT
                        arrayMap(
                            x -> toUInt8(x),
                            ngrams(arrayJoin(splitByChar('\n', (SELECT input FROM input_wrapper)::String)), 1)
                        ) AS digits,
                        length(digits) AS total_digits
                ),


                -- Constants
                12 AS num_digits_needed,


                -- Part 1: Find largest two-digit number from each line
                part1_largest_pairs AS (
                    SELECT
                        ngrams(arrayJoin(splitByChar('\n', (SELECT input FROM input_wrapper)::String)), 1AS chars,
                        arraySlice(chars, 1, length(chars) - 1AS chars_without_last,


                        -- Find first max digit, then find max digit after it
                        concat(
                            arrayMax(chars_without_last),
                            arrayMax(
                                arraySlice(
                                    chars,
                                    arrayFirstIndex(
                                        x -> x = arrayMax(chars_without_last),
                                        chars
                                    ) + 1
                                )
                            )
                        )::Int16 AS largest_two_digit
                ),


                -- Part 2: Build largest N-digit number by greedily selecting max digits
                part2_greedy_selection AS (
                    SELECT
                        digits,


                        -- Iteratively build number by selecting maximum available digit
                        arrayFold(
                            (accumulator, current_element) -> (
                                -- Decrement remaining digits counter
                                greatest(accumulator.1 - 10)::Int64,


                                -- Update position: find where max digit is in remaining slice
                                accumulator.2 + (
                                    arrayFirstIndex(
                                        x -> x = arrayMax(
                                            arraySlice(
                                                digits,
                                                accumulator.2 + 1,
                                                total_digits - accumulator.1 - accumulator.2 + 1
                                            )
                                        ),
                                        arraySlice(
                                            digits,
                                            accumulator.2 + 1,
                                            total_digits - accumulator.1 - accumulator.2 + 1
                                        )
                                    )
                                )::UInt64,


                                -- Accumulate joltage: add max digit * 10^(remaining-1)
                                accumulator.3 + if(
                                    accumulator.1 = 0,
                                    0::UInt64,
                                    arrayMax(
                                        arraySlice(
                                            digits,
                                            accumulator.2 + 1,
                                            total_digits - accumulator.1 - accumulator.2 + 1
                                        )
                                    ) * intExp10(greatest(0, accumulator.1 - 1))
                                )
                            ),
                            digits,


                            -- Initial accumulator state:
                            -- (digits_remaining, current_position, accumulated_value)
                            (num_digits_needed::Int64, 0::UInt64, 0::UInt64)
                        ).3 AS joltage  -- Extract the accumulated value (3rd element)


                    FROM digit_array
                )


                -- Combine results from both parts
                SELECT
                    'Part 1' AS part,
                    sum(largest_two_digit)::UInt64 AS solution -- Should be 17263 with my input
                FROM part1_largest_pairs


                UNION ALL


                SELECT
                    'Part 2' AS part,
                    sum(joltage) AS solution -- Should be 170731717900423 with my input
                FROM part2_greedy_selection

                查看完整谜题描述:https://adventofcode.com/2025/day/3


                第 4 天:打印部门

                谜题:精灵们需要突破一面由纸卷堆叠而成的墙。这个谜题是 Conway 的生命游戏的一种变体。你会得到一个网格,其中 @ 表示一个纸卷的位置。

                • 第 1 部分定义了一条规则:如果一个纸卷周围的邻居数量少于 4 个,它就可以被“移除”。你需要统计在初始状态下满足这一条件的纸卷数量。

                • 第 2 部分要求递归地模拟这一过程。移除一个纸卷,可能会让原本无法移除的其他纸卷变得可移除。你需要不断执行这一过程,直到整个系统稳定下来,最后统计被移除的纸卷总数。

                我们是如何用 ClickHouse SQL 解决这个问题的:由于这是一个每一步都依赖前一步结果的迭代模拟问题,我们采用了递归 CTE。我们将网格表示为一组 (x, y) 坐标点。在每一次递归中,通过对这些点进行自连接来计算每个点的邻居数量。随后,只保留那些仍然“存活”的点(邻居数量 >= 4),其余点则被隐式地移除。我们持续执行递归,直到点的数量不再发生变化。


                实现细节:

                1. WITH RECURSIVE:我们使用标准 SQL 的递归 CTE 来完成这一基于邻接关系的遍历过程。初始步骤选出所有纸卷的位置,递归步骤则根据邻居数量对结果集进行不断收缩。

                  WITH RECURSIVE recursive_convergence AS (
                      -- Base case: all points
                      UNION ALL
                      -- Recursive step: keep points with >= 4 neighbors
                      SELECT ... HAVING countIf(...) >= 4
                  )

                  2. argMin:为了准确判断模拟过程在哪一刻达到稳定状态,我们在每一层递归深度上记录点的数量,并使用 argMin(point_count, depth)


                  完整解决方案:

                    WITH RECURSIVE 
                    -- Define puzzle input (grid with '@' symbols)
                    input_wrapper AS (SELECT raw_blob AS input FROM aoc.input4),


                    -- Split input into lines
                    input_lines AS (
                        SELECT splitByChar('\n', (SELECT input FROM input_wrapper)::String) AS lines
                    ),


                    -- Find all '@' symbol positions in the grid
                    grid_points AS (
                        SELECT arrayJoin(
                            arrayFlatten(
                                arrayMap(
                                    line_tuple -> 
                                        arrayMap(
                                            x_pos -> (x_pos, line_tuple.2),
                                            arrayFilter(
                                                (pos, val) -> val = '@',
                                                arrayEnumerate(line_tuple.1),
                                                line_tuple.1
                                            )
                                        ),
                                    arrayMap(
                                        (line, line_num) -> (ngrams(line, 1), line_num),
                                        lines,
                                        range(1, length(lines) + 1)
                                    )
                                )
                            )::Array(Tuple(UInt8, UInt8))
                        ) AS point
                        FROM input_lines
                    ),


                    -- Expand points into separate columns
                    initial_points AS (
                        SELECT 
                            point.1 AS x,
                            point.2 AS y
                        FROM grid_points
                    ),


                    -- Recursive CTE: Keep only points with 4+ neighbors at each iteration
                    recursive_convergence AS (
                        -- Base case: all initial points at depth 1
                        SELECT
                            x,
                            y,
                            1 AS depth
                        FROM initial_points


                        UNION ALL


                        -- Recursive case: keep points with at least 4 neighbors
                        SELECT
                            p.x,
                            p.y,
                            depth + 1 AS depth
                        FROM recursive_convergence AS p
                        CROSS JOIN recursive_convergence AS q
                        WHERE depth < 256  -- Maximum recursion depth
                        GROUP BY p.x, p.y, depth
                        HAVING countIf(
                            q.x BETWEEN p.x - 1 AND p.x + 1
                            AND q.y BETWEEN p.y - 1 AND p.y + 1
                            AND NOT (p.x = q.x AND p.y = q.y)
                        ) >= 4
                    ),


                    -- Track point counts at each depth level
                    depth_statistics AS (
                        SELECT 
                            depth,
                            count() AS point_count,
                            lagInFrame(point_count, 1OVER (ORDER BY depth) AS previous_count
                        FROM recursive_convergence
                        GROUP BY depth
                        ORDER BY depth
                    ),


                    -- Find the depth where the count stabilizes (no more points removed)
                    stabilization_analysis AS (
                        SELECT 
                            min(depth) AS stabilization_depth,
                            argMin(point_count, depth) AS stabilized_count
                        FROM depth_statistics
                        WHERE point_count = previous_count 
                            AND point_count > 0
                    ),


                    -- Part 1: Points removed after first iteration (depth 2)
                    part1_solution AS (
                        SELECT
                            (SELECT count() FROM initial_points) - 
                            (SELECT point_count FROM depth_statistics WHERE depth = 2 LIMIT 1AS solution
                    ),


                    -- Part 2: Points removed when convergence stabilizes
                    part2_solution AS (
                        SELECT
                            (SELECT count() FROM initial_points) - stabilized_count AS solution
                        FROM stabilization_analysis
                    ),


                    -- Combine results from both parts (necessary to prevent a bug with recursive CTE/external UNIONs)
                    combined_solutions AS (
                    SELECT
                        'Part 1' AS part,
                        solution -- Should be 1604 with my input
                    FROM part1_solution


                    UNION ALL


                    SELECT
                        'Part 2' AS part,
                        solution -- Should be 9397 with my input
                    FROM part2_solution)


                    select * from combined_solutions settings use_query_cache=true, query_cache_share_between_users = 1, query_cache_nondeterministic_function_handling = 'save', query_cache_ttl = 80000000, result_overflow_mode = 'throw', read_overflow_mode = 'throw'

                    查看完整谜题描述:https://adventofcode.com/2025/day/4



                    第 5 天:自助餐厅

                    谜题:精灵们在管理库存时遇到了麻烦,问题涉及一组被标记为“新鲜”的 ID 范围(例如 3-5、10-14)。

                    • 第 1 部分要求统计有多少个指定的物品 ID 落在任意一个新鲜范围之内。

                    • 第 2 部分则要求计算这些新鲜范围一共覆盖了多少个唯一的整数,也就是所有区间的并集。例如,当范围为 1-5 和 3-7 时,它们的并集是 1-7(大小为 7),而不是简单相加得到的 10。

                    我们是如何用 ClickHouse SQL 解决这个问题的:这是一个典型的区间运算问题。第 1 部分只需要做简单的范围判断过滤,而第 2 部分则需要将存在重叠的区间合并起来。手动实现区间合并逻辑往往相当复杂,但我们直接使用了 ClickHouse 提供的一个专用聚合函数,将原本复杂的算法问题压缩成了一行查询。


                    实现细节:

                    1. intervalLengthSum:我们使用这个专用聚合函数来计算区间并集的总长度。它能够自动处理区间之间的重叠和嵌套关系,使我们无需自行编写复杂的合并逻辑。

                      SELECT intervalLengthSum(range_tuple.1, range_tuple.2) AS solution

                      2. arrayExists:在第 1 部分中,我们使用 arrayExists 来判断某个指定的 ID 是否落在数组中的任意一个有效区间内。这样就可以在不将区间展开为海量行数据的情况下,高效完成判断。


                      完整解决方案:

                        -- Define puzzle input
                        WITH input_wrapper AS (SELECT trimRight(raw_blob,'\n'AS input FROM aoc.input5),


                        -- Split input into two sections
                        input_sections AS (
                            SELECT
                                splitByString('\n\n', (SELECT input FROM input_wrapper)::String)[1AS ranges_section,
                                splitByString('\n\n', (SELECT input FROM input_wrapper)::String)[2AS ids_section
                        ),


                        -- Parse ranges from first section (format: "min-max" per line)
                        parsed_ranges AS (
                            SELECT arrayMap(
                                x -> (
                                    toUInt64(splitByChar('-', x)[1]),
                                    toUInt64(splitByChar('-', x)[2]) + 1 -- Make max half-open
                                ),
                                splitByChar('\n', ranges_section)
                            ) AS ranges
                            FROM input_sections
                        ),


                        -- Parse IDs from second section (one ID per line)
                        parsed_ids AS (
                            SELECT arrayMap(
                                x -> toUInt64(x),
                                splitByChar('\n', ids_section)
                            ) AS ids
                            FROM input_sections
                        ),


                        -- Explode ranges into individual rows for Part 2 interval calculation
                        exploded_ranges AS (
                            SELECT arrayJoin(ranges) AS range_tuple
                            FROM parsed_ranges
                        ),


                        -- Part 1: Count how many IDs fall within any range
                        part1_solution AS (
                            SELECT
                                length(
                                    arrayFilter(
                                        id -> arrayExists(
                                            range -> id BETWEEN range.1 AND range.2,
                                            ranges
                                        ),
                                        ids
                                    )
                                ) AS solution
                            FROM parsed_ranges, parsed_ids
                        ),


                        -- Part 2: Calculate total interval length (union of all ranges)
                        part2_solution AS (
                            SELECT
                                intervalLengthSum(range_tuple.1, range_tuple.2AS solution
                            FROM exploded_ranges
                        )


                        -- Combine results from both parts
                        SELECT
                            'Part 1' AS part,
                            solution -- Should be 707 with my input
                        FROM part1_solution


                        UNION ALL


                        SELECT
                            'Part 2' AS part,
                            solution -- Should be 361615643045059 with my input
                        FROM part2_solution;

                        查看完整谜题描述:https://adventofcode.com/2025/day/5


                        第 6 天:垃圾压缩机

                        谜题:你发现了一张数学练习表,上面的计算题按列排列。

                        • 第 1 部分将输入理解为由空格分隔的数字列。你需要根据底部给出的运算符,对每一列的数字执行求和或相乘操作。

                        • 第 2 部分则揭示,这些输入是以“从右向左”的方式按列书写的,一个数字的各个数位被垂直堆叠在一起。你需要重新解析整个网格,将这些数位重新组合成数字,再按空白列进行分组,并应用对应的运算符。

                        我们是如何用 ClickHouse SQL 解决这个问题的:这一谜题的关键在于文本解析和数组操作。我们将输入文本当作一个二维字符矩阵来处理。为了从按行书写的文本形式切换到按列组织的数学题,我们本质上执行了一次“矩阵转置”。具体来说,我们先将每一行文本转换为字符数组,再对其进行“旋转”以按列处理,最后使用数组函数重建数字并完成运算。


                        实现细节:

                        1. splitByWhitespace:在第 1 部分中,我们使用该函数解析“横向”的表示方式。它能够自动处理列之间不固定的空格数量,这是简单字符串拆分难以正确处理的。

                        2. arrayProduct:由于 ClickHouse 并没有提供标准的 product() 聚合函数,我们将每一列映射为整数数组,并通过 arrayProduct 来完成乘法计算。

                          toInt64(arrayProduct(
                              arrayMap(x -> toInt64(x), arraySlice(column, 1, length(column) - 1))
                          ))

                          3. arraySplit:在第 2 部分中,在提取出所有原始数字之后,我们需要将它们拆分成独立的数学表达式。我们使用 arraySplit 在遇到运算符列时对大数组进行切分,从而有效地区分不同的计算问题。


                          完整解决方案:

                            -- Define puzzle input
                            WITH input_wrapper AS (SELECT trimRight(raw_blob,'\n'AS input FROM aoc.input6),


                            -- Part 1: Parse input into columns and apply operations
                            part1_parsed_rows AS (
                                SELECT arrayMap(
                                    x -> splitByWhitespace(x),
                                    splitByChar('\n', (SELECT input FROM input_wrapper)::String)
                                ) AS rows
                            ),


                            part1_columns AS (
                                SELECT arrayMap(
                                    column_index -> arrayMap(
                                        row -> row[column_index],
                                        rows
                                    ),
                                    range(1, length(rows[1]) + 1)
                                ) AS columns
                                FROM part1_parsed_rows
                            ),


                            part1_solution AS (
                                SELECT arraySum(
                                    arrayMap(
                                        column -> if(
                                            -- Check if last element is multiplication operator
                                            arrayLast(x -> 1column= '*',


                                            -- Multiply all numbers in column
                                            toInt64(arrayProduct(
                                                arrayMap(
                                                    x -> toInt64(x),
                                                    arraySlice(column1, length(column- 1)
                                                )
                                            )),


                                            -- Otherwise, add all numbers in column
                                            toInt64(arraySum(
                                                arrayMap(
                                                    x -> toInt64(x),
                                                    arraySlice(column1, length(column- 1)
                                                )
                                            ))
                                        ),
                                        columns
                                    )
                                ) AS solution
                                FROM part1_columns
                            ),


                            -- Part 2: Parse with character-level precision to handle multi-digit numbers
                            part2_parsed_chars AS (
                                SELECT arrayMap(
                                    x -> ngrams(x, 1),
                                    splitByChar('\n', (SELECT input FROM input_wrapper)::String)
                                ) AS char_rows
                            ),


                            part2_columns_raw AS (
                                SELECT arrayMap(
                                    column_index -> arrayMap(
                                        row -> row[column_index],
                                        char_rows
                                    ),
                                    range(1, length(char_rows[1]) + 1)
                                ) AS columns
                                FROM part2_parsed_chars
                            ),


                            part2_columns_filtered AS (
                                SELECT arrayFilter(
                                    x -> NOT arrayAll(y -> y = ' ', x),
                                    columns
                                ) AS non_empty_columns
                                FROM part2_columns_raw
                            ),


                            part2_numbers_extracted AS (
                                SELECT arrayMap(
                                    column -> replaceAll(
                                        arrayStringConcat(
                                            arraySlice(column1, length(column- 1)
                                        ),
                                        ' ',
                                        ''
                                    ),
                                    non_empty_columns
                                ) AS number_strings
                                FROM part2_columns_filtered
                            ),


                            part2_numbers_grouped AS (
                                SELECT
                                    number_strings,
                                    non_empty_columns,


                                    -- Split numbers by operator positions
                                    arraySplit(
                                        (number_str, has_operator) -> has_operator,
                                        number_strings,
                                        arrayMap(
                                            column -> hasAny(column, ['+''*']),
                                            non_empty_columns
                                        )
                                    ) AS number_groups
                                FROM part2_numbers_extracted, part2_columns_filtered
                            ),


                            part2_operations AS (
                                SELECT arrayZip(
                                    -- Extract operators from columns
                                    arrayFilter(
                                        x -> has(['+''*'], x),
                                        arrayFlatten(non_empty_columns)
                                    ),
                                    -- Pair with corresponding number groups
                                    number_groups
                                ) AS operations_with_numbers
                                FROM part2_numbers_grouped
                            ),


                            part2_solution AS (
                                SELECT arraySum(
                                    arrayMap(
                                        operation -> if(
                                            -- Check operator type
                                            operation.1 = '*',


                                            -- Multiply all numbers in group
                                            toInt64(arrayProduct(
                                                arrayMap(
                                                    x -> toInt64(x),
                                                    operation.2
                                                )
                                            )),


                                            -- Otherwise, add all numbers in group
                                            toInt64(arraySum(
                                                arrayMap(
                                                    x -> toInt64(x),
                                                    operation.2
                                                )
                                            ))
                                        ),
                                        operations_with_numbers
                                    )
                                ) AS solution
                                FROM part2_operations
                            )


                            -- Combine results from both parts
                            SELECT
                                'Part 1' AS part,
                                solution -- 5782351442566 with my input
                            FROM part1_solution


                            UNION ALL


                            SELECT
                                'Part 2' AS part,
                                solution -- 10194584711842 with my input
                            FROM part2_solution;

                            查看完整谜题描述:https://adventofcode.com/2025/day/6


                            第 7 天:传送实验室

                            谜题:你正在分析一个网格中的快子束传播过程。

                            • 第 1 部分模拟一束光线向下传播。当它遇到分裂器 ^ 时,会分裂成两束,分别向左和向右延伸。你需要统计整个过程中发生的分裂次数。

                            • 第 2 部分引入了“量子多世界”的设定:这一次,不是光束在分裂,而是宇宙本身发生分裂。你需要计算在网格底部最终存在的所有活跃“时间线”(路径)的数量。

                            我们是如何用 ClickHouse SQL 解决这个问题的:如果对每一条路径进行显式模拟,计算量会呈指数级增长。相反,我们将问题建模为一种波传播过程,类似于帕斯卡三角形的计算方式。我们使用 arrayFold 按行处理整个网格,在每一行中维护一个映射,记录每一列位置上的“世界”数量,并根据分裂器的规则,计算这些数量如何传递到下一行。


                            实现细节:

                            1. arrayFold:我们使用 arrayFold 来实现按行推进的模拟状态机。在这个过程中,我们携带一个复杂的状态对象——(left_boundary, right_boundary, worlds_map, part1_counter),并在处理网格的每一行时持续更新它。

                            2. sumMap:为了处理多条光束在同一位置汇合的情况(例如左分支和右分支最终落在同一坐标),我们使用 sumMap 对 world 映射中相同键对应的值进行聚合,从而自然地合并汇聚到同一位置的“时间线”数量。

                              arrayReduce('sumMap', arrayMap(position -> map(...), ...))

                              完整解决方案:

                                -- Define puzzle input 
                                WITH input_wrapper AS (SELECT raw_blob AS input FROM aoc.input7),


                                -- Parse input into character grid
                                parsed_grid AS (
                                    SELECT arrayMap(
                                        x -> ngrams(x, 1),
                                        splitByChar('\n', (SELECT input FROM input_wrapper)::String)
                                    ) AS rows
                                ),


                                -- Find starting position in first row
                                initial_state AS (
                                    SELECT
                                        arrayFirstIndex(x -> x = 'S'rows[1])::UInt8 AS start_position,
                                        map(
                                            arrayFirstIndex(x -> x = 'S'rows[1])::UInt8,
                                            1::UInt64
                                        )::Map(UInt8, UInt64) AS initial_worlds
                                    FROM parsed_grid
                                ),


                                -- Filter to only rows with '^' markers (active rows)
                                active_rows AS (
                                    SELECT arrayFilter(
                                        x -> has(x, '^'),
                                        rows
                                    ) AS filtered_rows
                                    FROM parsed_grid
                                ),


                                -- Main iteration: propagate world counts through rows
                                world_propagation AS (
                                    SELECT
                                        start_position,
                                        initial_worlds,
                                        filtered_rows,


                                        -- Fold through each row, updating state
                                        arrayFold(
                                            (accumulator, current_row-> (
                                                -- Update left boundary (shrink inward)
                                                (accumulator.1 - 1)::UInt8,


                                                -- Update right boundary (shrink inward)
                                                (accumulator.2 + 1)::UInt8,


                                                -- Update world map: propagate counts based on '^' positions
                                                mapSort(
                                                    (key, value-> key,
                                                    mapUpdate(
                                                        accumulator.3,
                                                        arrayReduce(
                                                            'sumMap',
                                                            arrayMap(
                                                                position -> if(
                                                                    -- Check if position has '^' and exists in current worlds
                                                                    current_row[position] = '^'
                                                                    AND mapContains(accumulator.3, position),


                                                                    -- Propagate world count to adjacent positions
                                                                    map(
                                                                        -- Left neighbor gets count (unless blocked by another '^')
                                                                        (position - 1)::UInt8,
                                                                        (
                                                                            accumulator.3[position] + if(
                                                                                current_row[greatest(0, position - 2)] = '^',
                                                                                0,
                                                                                accumulator.3[position - 1]
                                                                            )
                                                                        )::UInt64,


                                                                        -- Current position resets to 0
                                                                        (position)::UInt8,
                                                                        0::UInt64,


                                                                        -- Right neighbor gets count
                                                                        (position + 1)::UInt8,
                                                                        (accumulator.3[position + 1+ accumulator.3[position])::UInt64
                                                                    ),


                                                                    -- No propagation if conditions not met
                                                                    map()::Map(UInt8, UInt64)
                                                                ),
                                                                -- Only process positions within current boundaries
                                                                arraySlice(
                                                                    arrayEnumerate(current_row),
                                                                    accumulator.1,
                                                                    (accumulator.2 - accumulator.1+ 1
                                                                )
                                                            )
                                                        )
                                                    )
                                                ),


                                                -- Part 1 counter: count '^' positions with non-zero worlds
                                                accumulator.4 + arrayCount(
                                                    position -> 
                                                        current_row[position] = '^'
                                                        AND mapContains(accumulator.3, position)
                                                        AND accumulator.3[position] > 0,
                                                    arraySlice(
                                                        arrayEnumerate(current_row),
                                                        accumulator.1,
                                                        (accumulator.2 - accumulator.1+ 1
                                                    )
                                                )
                                            ),
                                            filtered_rows,


                                            -- Initial accumulator state:
                                            -- (left_boundary, right_boundary, worlds_map, part1_counter)
                                            (
                                                start_position,
                                                start_position,
                                                initial_worlds,
                                                0::UInt64
                                            )
                                        ) AS final_state
                                    FROM initial_state, active_rows
                                ),


                                -- Part 1: Count of '^' positions encountered with non-zero worlds
                                part1_solution AS (
                                    SELECT final_state.4 AS solution
                                    FROM world_propagation
                                ),


                                -- Part 2: Sum of all world counts across all positions
                                part2_solution AS (
                                    SELECT arraySum(mapValues(final_state.3)) AS solution
                                    FROM world_propagation
                                )


                                -- Combine results from both parts
                                SELECT
                                    'Part 1' AS part,
                                    solution -- 1633 with my input
                                FROM part1_solution


                                UNION ALL


                                SELECT
                                    'Part 2' AS part,
                                    solution -- 34339203133559 with my input
                                FROM part2_solution;

                                查看完整谜题描述:https://adventofcode.com/2025/day/7


                                第 8 天:游乐场

                                谜题:精灵们正在连接三维空间中的电气接线盒。

                                • 第 1 部分要求找出距离最近的 1000 对点并将它们连接起来,然后分析由这些连接形成的电路规模,也就是各个连通分量的大小。

                                • 第 2 部分则要求不断连接当前距离最近的点,直到所有接线盒最终连成一个整体的大电路。这在算法上对应的是一个最小生成树问题。

                                我们是如何用 ClickHouse SQL 解决这个问题的:这是一个典型的图论问题,需要使用并查集(disjoint-set union-find)的思路。我们首先生成点之间所有可能的边,并按距离从小到大排序。接着,使用 arrayFold 依次遍历这些边,只要一条边连接了两个原本不在同一集合中的点,就将这两个集合合并,从而逐步构建连通分量。


                                实现细节:

                                1. L2Distance:我们使用 ClickHouse 原生提供的 L2Distance 函数,高效地计算三维坐标 [x, y, z] 之间的欧几里得距离,从而可以按连接长度对所有候选边进行排序。

                                2. runningAccumulate:在第 2 部分中,我们需要判断何时已经包含了足够多的不同点,从而形成一个单一的电路。与其在每一行上反复执行代价高昂的 DISTINCT 统计,我们使用 uniqCombinedState 构建唯一元素的紧凑状态表示,再通过 runningAccumulate 按行合并这些状态,高效地得到唯一点数量的累计结果。

                                  runningAccumulate(points_state) AS unique_points_seen

                                  完整解决方案:

                                    -- Define puzzle input 
                                    WITH input_wrapper AS (SELECT raw_blob AS input FROM aoc.input8),


                                    -- Parse 3D coordinate points
                                    parsed_points AS (
                                        SELECT (x, y, z) AS point
                                        FROM format('CSV''x UInt32, y UInt32, z UInt32', (SELECT input FROM input_wrapper)::String)
                                    ),


                                    -- Generate all point pairs with L2 distances, sorted by distance
                                    point_pairs_by_distance AS (
                                        SELECT
                                            t1.point AS point1,
                                            t2.point AS point2,
                                            L2Distance(
                                                [point1.1, point1.2, point1.3],
                                                [point2.1, point2.2, point2.3]
                                            ) AS distance
                                        FROM parsed_points AS t1
                                        CROSS JOIN parsed_points AS t2
                                        WHERE point1 < point2
                                        ORDER BY distance ASC
                                    ),


                                    -- Take the 1000 closest pairs
                                    closest_pairs AS (
                                        SELECT groupArray([point1, point2]) AS pairs
                                        FROM (
                                            SELECT point1, point2
                                            FROM point_pairs_by_distance
                                            ORDER BY distance ASC
                                            LIMIT 1000
                                        )
                                    ),


                                    -- Part 1: Build connected components from closest pairs
                                    connected_components AS (
                                        SELECT
                                            pairs,


                                            -- Fold through pairs to merge into connected components
                                            arrayFold(
                                                (accumulator, pair) -> if(
                                                    -- Check if any existing components contain points from current pair
                                                    length(
                                                        arrayFilter(
                                                            component -> hasAny(component, pair),
                                                            accumulator
                                                        )
                                                    ) > 0,


                                                    -- Merge matching components with current pair
                                                    arrayConcat(
                                                        -- Keep non-matching components unchanged
                                                        arrayFilter(
                                                            component -> NOT hasAny(component, pair),
                                                            accumulator
                                                        ),
                                                        -- Add merged component
                                                        [
                                                            arrayDistinct(
                                                                arrayFlatten(
                                                                    arrayConcat(
                                                                        arrayFilter(
                                                                            component -> hasAny(component, pair),
                                                                            accumulator
                                                                        ),
                                                                        [pair]
                                                                    )
                                                                )
                                                            )
                                                        ]
                                                    ),


                                                    -- No matches found, add pair as new component
                                                    arrayConcat(accumulator, [pair])
                                                ),
                                                pairs,
                                                []::Array(Array(Tuple(UInt32, UInt32, UInt32)))
                                            ) AS components
                                        FROM closest_pairs
                                    ),


                                    component_analysis AS (
                                        SELECT
                                            components,
                                            arrayMap(x -> length(x), components) AS component_sizes
                                        FROM connected_components
                                    ),


                                    part1_solution AS (
                                        SELECT arrayProduct(
                                            arraySlice(
                                                arrayReverseSort(component_sizes),
                                                1,
                                                3
                                            )
                                        ) AS solution
                                        FROM component_analysis
                                    ),


                                    -- Part 2: Find first pair where 1000 unique points have been seen
                                    point_pair_states AS (
                                        SELECT
                                            point1,
                                            point2,
                                            distance,
                                            arrayReduce('uniqCombinedState', [point1, point2]) AS points_state
                                        FROM point_pairs_by_distance
                                    ),


                                    part2_solution AS (
                                        SELECT
                                            point1,
                                            point2,
                                            distance,
                                            runningAccumulate(points_state) AS unique_points_seen,
                                            point1.1 * point2.1 AS solution
                                        FROM point_pair_states
                                        WHERE unique_points_seen >= 1000
                                        ORDER BY distance ASC
                                        LIMIT 1
                                    )


                                    -- Combine results from both parts
                                    SELECT
                                        'Part 1' AS part,
                                        solution::UInt64 AS solution -- 135169 with my input
                                    FROM part1_solution


                                    UNION ALL


                                    SELECT
                                        'Part 2' AS part,
                                        solution::UInt64 AS solution -- 302133440 with my input
                                    FROM part2_solution


                                    SETTINGS allow_deprecated_error_prone_window_functions = 1;

                                    查看完整谜题描述:https://adventofcode.com/2025/day/8


                                    第 9 天:电影院

                                    谜题:电影院的地板可以表示为一个网格,其中分布着一些红色瓷砖。

                                    • 第 1 部分要求找出以任意两块红色瓷砖作为对角点时,所能形成的最大面积矩形。

                                    • 第 2 部分在此基础上增加了约束:这个矩形必须完全位于由所有红色和绿色瓷砖围成的闭合环路内部。

                                    我们是如何用 ClickHouse SQL 解决这个问题的:我们没有把它当作一个逐格搜索的问题,而是建模为几何计算问题。我们构造了表示候选矩形以及边界环路的多边形对象。通过将矩形边界转换为“环”结构,就可以直接使用 ClickHouse 原生的几何函数来计算面积并判断包含关系。


                                    实现细节:

                                    1. polygonAreaCartesian:我们通过为候选矩形构造对应的多边形对象,并直接使用 polygonAreaCartesian 计算面积,从而避免了手动计算宽度和高度。

                                    2. polygonsWithinCartesian:为了判断矩形是否完全位于环路内部,我们使用了这个几何包含函数。这里采用了一个技巧:由于几何计算在处理恰好落在边界上的点时容易出现问题,我们将候选矩形略微向内缩小(缩小 0.01 个单位),从而确保包含性检查严格成立,避免因边界重合带来的误判。

                                      -- Create slightly inset test bounds (0.01 units inside)
                                      (least(x1, x2) + 0.01, least(y1, y2) + 0.01AS bottom_left, ...
                                      polygonsWithinCartesian(test_bounds, all_points_ring)

                                      完整解决方案:

                                        -- Define puzzle input 
                                        WITH input_wrapper AS (SELECT raw_blob AS input FROM aoc.input9),


                                        -- Parse 2D coordinate points
                                        parsed_points AS (
                                            SELECT *
                                            FROM format('CSV''x Float64, y Float64', (SELECT input FROM input_wrapper)::String)
                                        ),


                                        -- Generate all unique pairs of points
                                        point_pairs AS (
                                            SELECT
                                                c1.x AS x1,
                                                c1.y AS y1,
                                                c2.x AS x2,
                                                c2.y AS y2
                                            FROM parsed_points AS c1
                                            CROSS JOIN parsed_points AS c2
                                            WHERE (c1.x, c1.y) < (c2.x, c2.y)
                                        ),


                                        -- Create bounding box polygons for each pair
                                        bounding_boxes AS (
                                            SELECT
                                                x1,
                                                y1,
                                                x2,
                                                y2,


                                                -- Exact bounding box (corners at point coordinates)
                                                [
                                                    (least(x1, x2), least(y1, y2)),        -- bottom-left
                                                    (least(x1, x2), greatest(y1, y2)),     -- top-left
                                                    (greatest(x1, x2), greatest(y1, y2)),  -- top-right
                                                    (greatest(x1, x2), least(y1, y2)),     -- bottom-right
                                                    (least(x1, x2), least(y1, y2))         -- close the ring
                                                ]::Ring AS exact_bounds,


                                                -- Expanded bounding box (extends 0.5 units beyond points)
                                                [
                                                    (least(x1, x2) - 0.5, least(y1, y2) - 0.5),        -- bottom-left
                                                    (least(x1, x2) - 0.5, greatest(y1, y2) + 0.5),     -- top-left
                                                    (greatest(x1, x2) + 0.5, greatest(y1, y2) + 0.5),  -- top-right
                                                    (greatest(x1, x2) + 0.5, least(y1, y2) - 0.5),     -- bottom-right
                                                    (least(x1, x2) - 0.5, least(y1, y2) - 0.5)         -- close the ring
                                                ]::Ring AS expanded_bounds
                                            FROM point_pairs
                                        ),


                                        -- Create polygon containing all points (for Part 2 containment test)
                                        all_points_array AS (
                                            SELECT groupArray((x, y)) AS points_array
                                            FROM parsed_points
                                        ),


                                        all_points_polygon AS (
                                            SELECT arrayPushBack(points_array, points_array[1])::Ring AS ring
                                            FROM all_points_array
                                        ),


                                        -- Part 1: Find largest bounding box by area
                                        part1_candidates AS (
                                            SELECT
                                                x1,
                                                y1,
                                                x2,
                                                y2,
                                                exact_bounds,
                                                expanded_bounds,
                                                polygonAreaCartesian(expanded_bounds) AS area
                                            FROM bounding_boxes
                                            ORDER BY area DESC
                                            LIMIT 1
                                        ),


                                        part1_solution AS (
                                            SELECT area AS solution
                                            FROM part1_candidates
                                        ),


                                        -- Part 2: Find largest bounding box that contains all points
                                        part2_candidates AS (
                                            SELECT
                                                bb.x1,
                                                bb.y1,
                                                bb.x2,
                                                bb.y2,


                                                -- Create slightly inset test bounds (0.01 units inside)
                                                (least(x1, x2) + 0.01, least(y1, y2) + 0.01AS bottom_left,
                                                (least(x1, x2) + 0.01, greatest(y1, y2) - 0.01AS top_left,
                                                (greatest(x1, x2) - 0.01, greatest(y1, y2) - 0.01AS top_right,
                                                (greatest(x1, x2) - 0.01, least(y1, y2) + 0.01AS bottom_right,


                                                -- Create test bounds polygon
                                                [
                                                    bottom_left,
                                                    top_left,
                                                    top_right,
                                                    bottom_right,
                                                    bottom_left
                                                ]::Ring AS test_bounds,


                                                -- Check if all points are within test bounds
                                                polygonsWithinCartesian(test_bounds, app.ring) AS all_points_contained,


                                                polygonAreaCartesian(bb.expanded_bounds) AS area
                                            FROM bounding_boxes AS bb
                                            CROSS JOIN all_points_polygon AS app
                                            WHERE all_points_contained != 0
                                            ORDER BY area DESC
                                            LIMIT 1
                                        ),


                                        part2_solution AS (
                                            SELECT area AS solution
                                            FROM part2_candidates
                                        )


                                        -- Combine results from both parts
                                        SELECT
                                            'Part 1' AS part,
                                            solution AS area -- 4739623064 with my input
                                        FROM part1_solution


                                        UNION ALL


                                        SELECT
                                            'Part 2' AS part,
                                            solution AS area -- 1654141440 with my input
                                        FROM part2_solution;

                                        查看完整谜题描述:https://adventofcode.com/2025/day/9


                                        第 10 天:工厂 

                                        谜题:你需要通过按下按钮来配置工厂中的机器。

                                        • 第 1 部分涉及通过切换灯光状态(XOR 逻辑)来匹配指定的目标模式。

                                        • 第 2 部分则需要通过递增“电压值”计数器,用尽可能少的按键次数达到一个非常大的目标整数。

                                        我们是如何用 ClickHouse SQL 解决这个问题的:在第 1 部分中,由于搜索空间相对较小,我们可以直接使用暴力枚举的方法,生成所有可能的按钮组合,并通过位掩码进行验证。第 2 部分则需要更高效的策略。我们在 SQL 中实现了一个自定义的递归折半算法,通过反复减去按钮的影响,并对剩余的目标值进行“折半”处理,逐步将巨大的目标数缩减到零。


                                        实现细节:

                                        1. bitTest 和 bitCount:我们将按钮组合表示为二进制整数。bitTest 用于判断某个组合中是否按下了指定按钮,而 bitCount 则用于统计该组合对应的总按键次数,也就是整体代价。

                                        2. ARRAY JOIN:为了构造第 1 部分的完整搜索空间,我们生成了一个整数范围(从 0 到 2^N),并使用 ARRAY JOIN 将其展开为多行数据,从而为每一种可能的按钮按压组合生成一行记录。

                                          ARRAY JOIN range(0, toUInt32(pow(2, num_buttons)))

                                          完整解决方案:

                                            WITH RECURSIVE
                                            -- Define puzzle input 
                                                input_wrapper AS (SELECT raw_blob AS input FROM aoc.input10),


                                            -- Parse raw input into structured format
                                            raw_split AS (
                                                SELECT
                                                    row_number() OVER () AS puzzle_id,
                                                    splitByChar(' ', raw) AS components
                                                FROM format('TSVRaw''raw String', (SELECT input FROM input_wrapper)::String)
                                            ),


                                            parsed_puzzles AS (
                                                SELECT
                                                    puzzle_id,


                                                    -- Parse diagram: '#' becomes 1, '.' becomes 0
                                                    arrayMap(
                                                        x -> if(x = '#'10),
                                                        ngrams(replaceRegexpAll(components[1], '[\\[\\]]'''), 1)
                                                    ) AS target_diagram,


                                                    -- Parse buttons: each button affects specific positions
                                                    arrayMap(
                                                        button_str -> arrayMap(
                                                            pos_str -> (toUInt16(pos_str) + 1),
                                                            splitByChar(',', replaceRegexpAll(button_str, '[\\(\\)]'''))
                                                        ),
                                                        arraySlice(components, 2, length(components) - 2)
                                                    ) AS button_effects,


                                                    -- Parse joltages: target values for Part 2
                                                    arrayMap(
                                                        x -> toUInt32(x),
                                                        splitByChar(',', replaceRegexpAll(components[-1], '[\\{\\}]'''))
                                                    ) AS target_joltages
                                                FROM raw_split
                                            ),


                                            puzzle_metadata AS (
                                                SELECT
                                                    puzzle_id,
                                                    target_diagram,
                                                    button_effects,
                                                    target_joltages,
                                                    length(button_effects) AS num_buttons,
                                                    length(target_joltages) AS num_positions
                                                FROM parsed_puzzles
                                            ),


                                            -- PART 1: Brute force - enumerate all button combinations
                                            part1_button_combinations AS (
                                                SELECT
                                                    p.puzzle_id,
                                                    p.target_diagram,
                                                    p.button_effects,
                                                    p.num_buttons,
                                                    p.num_positions,
                                                    combination_id,
                                                    toUInt32(bitCount(combination_id)) AS button_presses,


                                                    -- Calculate resulting diagram from this combination
                                                    arrayMap(
                                                        position -> toUInt8(
                                                            modulo(
                                                                arrayReduce(
                                                                    'sum',
                                                                    arrayMap(
                                                                        button_index -> if(
                                                                            bitTest(combination_id, button_index)
                                                                            AND has(button_effects[button_index + 1], position),
                                                                            1,
                                                                            0
                                                                        ),
                                                                        range(0, num_buttons)
                                                                    )
                                                                ),
                                                                2
                                                            )
                                                        ),
                                                        range(1, num_positions + 1)
                                                    ) AS resulting_diagram
                                                FROM puzzle_metadata p
                                                ARRAY JOIN range(0, toUInt32(pow(2, num_buttons))) AS combination_id
                                            ),


                                            part1_minimum_solutions AS (
                                                SELECT
                                                    puzzle_id,
                                                    min(button_presses) AS minimum_presses
                                                FROM part1_button_combinations
                                                WHERE target_diagram = resulting_diagram
                                                GROUP BY puzzle_id
                                            ),


                                            -- PART 2: Pre-compute button combination patterns for recursive algorithm
                                            button_combination_patterns AS (
                                                SELECT
                                                    p.puzzle_id,
                                                    p.button_effects,
                                                    p.num_buttons,
                                                    p.num_positions,
                                                    combination_id,
                                                    toUInt32(bitCount(combination_id)) AS pattern_cost,


                                                    -- Pattern: numeric effect on each position
                                                    arrayMap(
                                                        position -> toUInt32(
                                                            arrayReduce(
                                                                'sum',
                                                                arrayMap(
                                                                    button_index -> if(
                                                                        bitTest(combination_id, button_index)
                                                                        AND has(button_effects[button_index + 1], position),
                                                                        1,
                                                                        0
                                                                    ),
                                                                    range(0, num_buttons)
                                                                )
                                                            )
                                                        ),
                                                        range(1, num_positions + 1)
                                                    ) AS effect_pattern,


                                                    -- Parity pattern: XOR constraint (mod 2)
                                                    arrayMap(
                                                        position -> toUInt8(
                                                            modulo(
                                                                arrayReduce(
                                                                    'sum',
                                                                    arrayMap(
                                                                        button_index -> if(
                                                                            bitTest(combination_id, button_index)
                                                                            AND has(button_effects[button_index + 1], position),
                                                                            1,
                                                                            0
                                                                        ),
                                                                        range(0, num_buttons)
                                                                    )
                                                                ),
                                                                2
                                                            )
                                                        ),
                                                        range(1, num_positions + 1)
                                                    ) AS parity_pattern
                                                FROM puzzle_metadata p
                                                ARRAY JOIN range(0, toUInt32(pow(2, num_buttons))) AS combination_id
                                            ),


                                            -- Group patterns by parity for efficient lookup
                                            patterns_grouped_by_parity AS (
                                                SELECT
                                                    puzzle_id,
                                                    button_effects,
                                                    num_buttons,
                                                    num_positions,
                                                    parity_pattern,
                                                    groupArray(tuple(effect_pattern, pattern_cost)) AS available_patterns
                                                FROM button_combination_patterns
                                                GROUP BY puzzle_id, button_effects, num_buttons, num_positions, parity_pattern
                                            ),


                                            -- Recursive halving algorithm: iteratively reduce joltages to zero
                                            recursive_halving_solver AS (
                                                -- Base case: start with target joltages
                                                SELECT
                                                    puzzle_id,
                                                    button_effects,
                                                    num_buttons,
                                                    num_positions,
                                                    target_joltages AS current_goal,
                                                    toUInt64(0AS accumulated_cost,
                                                    0 AS recursion_depth
                                                FROM puzzle_metadata


                                                UNION ALL


                                                -- Recursive case: apply pattern, subtract, halve, and continue
                                                SELECT
                                                    puzzle_id,
                                                    button_effects,
                                                    num_buttons,
                                                    num_positions,
                                                    current_goal,
                                                    min(accumulated_cost) AS accumulated_cost,
                                                    min(recursion_depth) AS recursion_depth
                                                FROM (
                                                    SELECT
                                                        solver.puzzle_id,
                                                        solver.button_effects,
                                                        solver.num_buttons,
                                                        solver.num_positions,


                                                        -- New goal: (current - pattern) 2
                                                        arrayMap(
                                                            i -> intDiv(
                                                                solver.current_goal[i] - pattern_tuple.1[i],
                                                                2
                                                            ),
                                                            range(1, solver.num_positions + 1)
                                                        ) AS current_goal,


                                                        -- Accumulate cost: pattern_cost * 2^depth
                                                        solver.accumulated_cost + 
                                                            toUInt64(pattern_tuple.2* toUInt64(pow(2, solver.recursion_depth)) AS accumulated_cost,


                                                        solver.recursion_depth + 1 AS recursion_depth
                                                    FROM recursive_halving_solver solver
                                                    INNER JOIN patterns_grouped_by_parity patterns
                                                        ON patterns.puzzle_id = solver.puzzle_id
                                                        AND patterns.parity_pattern = arrayMap(
                                                            x -> if(x % 2 = 0, toUInt8(0), toUInt8(1)),
                                                            solver.current_goal
                                                        )
                                                    ARRAY JOIN patterns.available_patterns AS pattern_tuple
                                                    WHERE
                                                        solver.recursion_depth < 100
                                                        AND NOT arrayAll(x -> x = 0, solver.current_goal)
                                                        -- Ensure pattern doesn't overshoot (feasibility constraint)
                                                        AND arrayAll(
                                                            i -> pattern_tuple.1[i] <= solver.current_goal[i],
                                                            range(1, solver.num_positions + 1)
                                                        )
                                                )
                                                GROUP BY puzzle_id, button_effects, num_buttons, num_positions, current_goal
                                            ),


                                            part2_minimum_solutions AS (
                                                SELECT
                                                    puzzle_id,
                                                    min(accumulated_cost) AS minimum_cost
                                                FROM recursive_halving_solver
                                                WHERE arrayAll(x -> x = 0, current_goal)
                                                GROUP BY puzzle_id
                                            ),


                                            -- Aggregate final solutions
                                            combined_solutions AS (
                                                SELECT 'Part 1' AS part, sum(minimum_presses) AS solution -- 527 with my input
                                                FROM part1_minimum_solutions


                                                UNION ALL


                                                SELECT 'Part 2' AS part, sum(minimum_cost) AS solution -- 19810 with my input
                                                FROM part2_minimum_solutions
                                            )


                                            -- Combine results from both parts
                                            SELECT * FROM combined_solutions settings use_query_cache=true, query_cache_share_between_users = 1, query_cache_nondeterministic_function_handling = 'save', query_cache_ttl = 80000000, result_overflow_mode = 'throw', read_overflow_mode = 'throw';

                                            查看完整谜题描述:https://adventofcode.com/2025/day/10


                                            第 11 天:反应堆 

                                            谜题:你正在调试一个反应堆的控制图结构。

                                            • 第 1 部分要求统计从你当前所在的节点到 out 节点之间所有不同路径的数量。

                                            • 第 2 部分则要求统计从 svr 到 out 的路径数量,并且必须满足一个额外条件:路径需要同时经过中间节点 dac 和 fft。

                                            我们是如何用 ClickHouse SQL 解决这个问题的:我们使用递归 CTE 对这张图进行遍历。为了在第 2 部分中满足路径约束条件,我们在递归过程中携带了“是否访问过”的状态标志。在遍历过程中,只要遇到对应的关键节点,就更新相应的布尔标志。最终,只需筛选出两个标志都为 true 的路径即可。


                                            实现细节:

                                            1. cityHash64:在大规模递归 JOIN 中,字符串比较往往代价较高。为此,我们使用 cityHash64 将节点名称(例如 svr、dac)转换为确定性的 64 位整数,从而显著提升 JOIN 的执行速度,并降低整体内存占用。

                                              cityHash64('svr'AS svr_node

                                              2. 状态跟踪:我们在递归结果表中额外添加了布尔字段,用于跟踪路径状态。这使我们能够在一次遍历中完成“必须同时访问 X 和 Y”这样的约束判断,而无需额外的复杂后处理逻辑。

                                                paths.visited_dac OR (edges.to_node = kn.dac_node) AS visited_dac

                                                完整解决方案:

                                                  WITH RECURSIVE
                                                  -- Define puzzle input 
                                                  input_wrapper AS (SELECT raw_blob AS input FROM aoc.input11),


                                                  -- Define key node identifiers
                                                  key_nodes AS (
                                                      SELECT
                                                          cityHash64('svr'AS svr_node,
                                                          cityHash64('you'AS you_node,
                                                          cityHash64('dac'AS dac_node,
                                                          cityHash64('fft'AS fft_node,
                                                          cityHash64('out'AS out_node
                                                  ),


                                                  -- Parse input connections
                                                  raw_connections AS (
                                                      SELECT splitByString(': ', raw) AS parsed_parts
                                                      FROM format('TSV''raw String', (SELECT input FROM input_wrapper)::String)
                                                  ),


                                                  parsed_connections AS (
                                                      SELECT
                                                          parsed_parts[1AS input_node,
                                                          splitByWhitespace(parsed_parts[2]) AS output_nodes
                                                      FROM raw_connections
                                                  ),


                                                  -- Create graph edges with hashed node IDs
                                                  graph_edges AS (
                                                      SELECT
                                                          cityHash64(input_node) AS from_node,
                                                          cityHash64(arrayJoin(output_nodes)) AS to_node
                                                      FROM parsed_connections
                                                  ),


                                                  -- Part 2: Count paths from 'svr' to 'out' that visit both 'dac' and 'fft'
                                                  paths_from_svr AS (
                                                      -- Base case: start at 'svr' node
                                                      SELECT
                                                          0 AS generation,
                                                          svr_node AS current_node,
                                                          0::UInt8 AS visited_dac,
                                                          0::UInt8 AS visited_fft,
                                                          1::UInt64 AS paths_count
                                                      FROM key_nodes


                                                      UNION ALL


                                                      -- Recursive case: traverse edges and track checkpoint visits
                                                      SELECT
                                                          generation,
                                                          current_node,
                                                          visited_dac,
                                                          visited_fft,
                                                          sum(paths_count) AS paths_count
                                                      FROM (
                                                          SELECT
                                                              paths.generation + 1 AS generation,
                                                              edges.to_node AS current_node,
                                                              paths.visited_dac OR (edges.to_node = kn.dac_node) AS visited_dac,
                                                              paths.visited_fft OR (edges.to_node = kn.fft_node) AS visited_fft,
                                                              paths.paths_count AS paths_count
                                                          FROM paths_from_svr paths
                                                          JOIN graph_edges edges ON edges.from_node = paths.current_node
                                                          CROSS JOIN key_nodes kn
                                                          WHERE
                                                              edges.to_node != kn.out_node
                                                              AND paths.generation < 628
                                                      )
                                                      GROUP BY generation, current_node, visited_dac, visited_fft
                                                  ),


                                                  -- Part 1: Count all paths from 'you' to 'out'
                                                  paths_from_you AS (
                                                      -- Base case: start at 'you' node
                                                      SELECT
                                                          0 AS generation,
                                                          you_node AS current_node,
                                                          1::UInt64 AS paths_count
                                                      FROM key_nodes


                                                      UNION ALL


                                                      -- Recursive case: traverse edges
                                                      SELECT
                                                          generation,
                                                          current_node,
                                                          sum(paths_count) AS paths_count
                                                      FROM (
                                                          SELECT
                                                              paths.generation + 1 AS generation,
                                                              edges.to_node AS current_node,
                                                              paths.paths_count AS paths_count
                                                          FROM paths_from_you paths
                                                          JOIN graph_edges edges ON edges.from_node = paths.current_node
                                                          CROSS JOIN key_nodes kn
                                                          WHERE
                                                              edges.to_node != kn.out_node
                                                              AND paths.generation < 628
                                                      )
                                                      GROUP BY generation, current_node
                                                  ),


                                                  -- Part 1 solution: paths from 'you' to 'out'
                                                  part1_solution AS (
                                                      SELECT sum(paths.paths_count) AS solution
                                                      FROM paths_from_you paths
                                                      JOIN graph_edges edges ON edges.from_node = paths.current_node
                                                      CROSS JOIN key_nodes kn
                                                      WHERE edges.to_node = kn.out_node
                                                  ),


                                                  -- Part 2 solution: paths from 'svr' to 'out' visiting both checkpoints
                                                  part2_solution AS (
                                                      SELECT sum(paths.paths_count) AS solution
                                                      FROM paths_from_svr paths
                                                      JOIN graph_edges edges ON edges.from_node = paths.current_node
                                                      CROSS JOIN key_nodes kn
                                                      WHERE
                                                          edges.to_node = kn.out_node
                                                          AND paths.visited_dac = 1
                                                          AND paths.visited_fft = 1
                                                  ),


                                                  solutions_combined as (
                                                  SELECT 
                                                      'Part 1' AS part,
                                                      (SELECT solution FROM part1_solution) AS solution -- 724 with my input


                                                  UNION ALL


                                                  SELECT 
                                                      'Part 2' AS part,
                                                      (SELECT solution FROM part2_solution) AS solution -- 473930047491888 with my input
                                                  )


                                                  SELECT * FROM solutions_combined;

                                                  查看完整谜题描述:https://adventofcode.com/2025/day/11


                                                  第 12 天:圣诞树农场

                                                  谜题:精灵们需要将形状不规则的礼物(由 # 组成的网格表示)打包进矩形区域中。乍一看,这像是一个复杂的二维装箱问题。不过,谜题的输入条件允许使用一种启发式的简化方法:只要礼物的总面积不超过区域的总面积,就可以认为是可行的。

                                                  我们是如何用 ClickHouse SQL 解决这个问题的:由于只需要进行面积层面的判断,解决方案的重点放在了解析上。我们将 ASCII 图案表示的礼物形状转换为二进制网格(由 1 和 0 组成的数组),并计算每种形状的面积(也就是 1 的数量)。接着,将每种礼物的需求数量与其面积相乘并求和,再与目标区域的总面积进行比较。


                                                  实现细节:

                                                  1. replaceRegexpAll:我们通过正则表达式替换,将可视化的 # 字符替换为 1,将 . 替换为 0。这样一来,原本用于展示的“图案”就变成了可以直接计算的二进制字符串,并进一步解析为数组。

                                                  2. arraySum:我们使用了带 lambda 的 arraySum 来完成类似“点积”的计算。通过将每种礼物的数量与其面积相乘,并在同一个表达式中求和,使实现保持简洁清晰。

                                                    arraySum(
                                                        (volume, area) -> volume * area,
                                                        requested_shape_volumes,
                                                        areas_per_shape
                                                    )

                                                    完整解决方案:

                                                      -- Define puzzle input 
                                                      WITH input_wrapper AS (SELECT trimRight(raw_blob,'\n'AS input FROM aoc.input12),


                                                      -- Split input into sections
                                                      input_sections AS (
                                                          SELECT arrayMap(
                                                              section -> splitByChar('\n', section),
                                                              splitByString('\n\n', (SELECT input FROM input_wrapper)::String)
                                                          ) AS sections
                                                      ),


                                                      -- Extract regions section (last section)
                                                      regions_section AS (
                                                          SELECT sections[-1AS region_lines
                                                          FROM input_sections
                                                      ),


                                                      -- Extract shape sections (all except last)
                                                      shapes_sections AS (
                                                          SELECT arrayJoin(
                                                              arraySlice(sections, 1, length(sections) - 1)
                                                          ) AS shape_lines
                                                          FROM input_sections
                                                      ),


                                                      -- Parse shape data
                                                      parsed_shapes AS (
                                                          SELECT
                                                              shape_lines,


                                                              -- Transform shape lines: first line is name, rest is pattern
                                                              arrayMap(
                                                                  line_index -> if(
                                                                      line_index = 1,
                                                                      -- First line: remove ':' from name
                                                                      replaceAll(shape_lines[line_index], ':'''),
                                                                      -- Other lines: convert '#' to 1, '.' to 0
                                                                      replaceRegexpAll(
                                                                          replaceRegexpAll(shape_lines[line_index], '#''1'),
                                                                          '\\.',
                                                                          '0'
                                                                      )
                                                                  ),
                                                                  arrayEnumerate(shape_lines)
                                                              ) AS transformed_lines
                                                          FROM shapes_sections
                                                      ),


                                                      -- Convert shape patterns to binary arrays
                                                      shape_patterns AS (
                                                          SELECT
                                                              transformed_lines,
                                                              arrayMap(
                                                                  line -> arrayMap(
                                                                      char -> toUInt8(char),
                                                                      ngrams(line, 1)
                                                                  ),
                                                                  arraySlice(transformed_lines, 2)
                                                              ) AS shape_grid
                                                          FROM parsed_shapes
                                                      ),


                                                      -- Calculate area needed for each shape
                                                      shape_areas AS (
                                                          SELECT groupArray(
                                                              arrayCount(
                                                                  cell -> cell = 1,
                                                                  arrayFlatten(shape_grid)
                                                              )
                                                          ) AS areas_per_shape
                                                          FROM shape_patterns
                                                      ),


                                                      -- Parse region specifications
                                                      parsed_regions AS (
                                                          SELECT
                                                              arrayJoin(
                                                                  arrayMap(
                                                                      line -> splitByString(': ', line),
                                                                      region_lines
                                                                  )
                                                              ) AS region_parts
                                                          FROM regions_section
                                                      ),


                                                      -- Calculate region dimensions and requested volumes
                                                      region_specifications AS (
                                                          SELECT
                                                              region_parts,


                                                              -- Calculate total region area (product of dimensions)
                                                              arrayProduct(
                                                                  arrayMap(
                                                                      dim -> toUInt32(dim),
                                                                      splitByChar('x', region_parts[1])
                                                                  )
                                                              ) AS total_region_area,


                                                              -- Extract requested volumes for each shape
                                                              arrayMap(
                                                                  vol -> toUInt8(vol),
                                                                  splitByChar(' ', region_parts[2])
                                                              ) AS requested_shape_volumes
                                                          FROM parsed_regions
                                                      ),


                                                      -- Check if each region can fit the requested shapes
                                                      region_fit_analysis AS (
                                                          SELECT
                                                              total_region_area,
                                                              requested_shape_volumes,
                                                              areas_per_shape,


                                                              -- Calculate total area needed: sum of (volume * area) for each shape
                                                              arraySum(
                                                                  (volume, area) -> volume * area,
                                                                  requested_shape_volumes,
                                                                  areas_per_shape
                                                              ) AS total_area_needed,


                                                              -- Check if shapes fit in region
                                                              total_area_needed <= total_region_area AS shapes_fit
                                                          FROM region_specifications
                                                          CROSS JOIN shape_areas
                                                      ),


                                                      -- Count regions where shapes fit
                                                      solution AS (
                                                          SELECT countIf(shapes_fit) AS solution
                                                          FROM region_fit_analysis
                                                      )


                                                      -- Return final answer
                                                      SELECT solution -- 463 with my input
                                                      FROM solution

                                                      查看完整谜题描述:https://adventofcode.com/2025/day/12


                                                      结论

                                                      我们成功地仅使用纯 ClickHouse SQL 解决了全部 12 个谜题,这充分展示了 ClickHouse 查询引擎的灵活性与表达能力。这一切得益于 ClickHouse 内置的庞大函数库,它在很大程度上弥合了 SQL 与通用编程语言之间的差距。在整个挑战过程中,我们使用了十多种字符串函数,并配合 format 表函数,将杂乱无章的输入整理成可处理的数据集。我们大量依赖了 arrayMap、arrayProduct 等数组函数,而真正发挥关键作用的是 arrayReduce 和 arrayFold,它们让我们能够实现复杂的函数式逻辑,并在多次迭代中持续维护状态。再结合用于路径查找的原生递归 CTE、用于三维几何计算的距离函数、用于空间分析的多边形函数,以及用于逻辑运算的位运算,ClickHouse 的表现更像是一个高性能的向量计算引擎,而不仅仅是传统意义上的数据库。

                                                      由于我们人为设定的限制条件,这些 SQL 方案在计算效率上可能不及使用 Rust 或 Python 等通用编程语言实现的版本,但它们依然能够产生完全一致的结果。这个实验表明,只要工具足够强大,那些在 SQL 中看似“不可能”的问题,对 ClickHouse 来说不过是另一类有趣的数据挑战。完整的解决方案查询可以在我们的 ClickHouse/TreeHouse 仓库中查看。

                                                      注意:Thomas Neumann 在 2024 年使用 Umbra DB 完成了全部 Advent of Code 谜题,他的相关成果可以在对应的仓库中找到。


                                                      /END/


                                                      试用阿里云 ClickHouse企业版


                                                      轻松节省30%云资源成本?阿里云数据库ClickHouse 云原生架构全新升级,首次购买ClickHouse企业版计算和存储资源组合,首月消费不超过99.58元(包含最大16CCU+450G OSS用量)了解详情:https://t.aliyun.com/Kz5Z0q9G



                                                      征稿启示

                                                      面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com

                                                      文章转载自ClickHouseInc,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                      评论