AdventOfCode 2023 Day 20

Part One

通过三种module的传递,计算出传递的low / high信号次数。有些模块需要存储,根据上一次信号类型,才能决定下一次的信号类型。

broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a

状态存储

通过一张state表来存储状态,并创建两个函数来进行状态判断

CREATE OR REPLACE FUNCTION flip(_module VARCHAR)
RETURNS VARCHAR AS $$
DECLARE
    pulse VARCHAR;
BEGIN
    UPDATE state SET status = CASE WHEN status = 'ON' THEN 'OFF' ELSE 'ON' END WHERE module = _module RETURNING status INTO pulse;
    IF pulse = 'OFF' THEN
      return 'low';
    ELSE
      return 'high';
    END IF;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION conjunction(_module VARCHAR, _pre_module VARCHAR, _pulse VARCHAR)
RETURNS VARCHAR AS $$
DECLARE
    pulse VARCHAR;
BEGIN
    UPDATE state 
    SET status = replace(replace(status, 
                                 _pre_module || ':low', 
                                 _pre_module || ':' || _pulse
                                ), 
                         _pre_module || ':high', 
                         _pre_module || ':' || _pulse
                 ) 
    WHERE module = _module RETURNING status INTO pulse;

    IF pulse like '%low%' THEN
      return 'high';
    ELSE
      return 'low';
    END IF;
END;
$$ LANGUAGE plpgsql;

状态需要初始化

WITH RECURSIVE origin AS (
  SELECT _row, CASE WHEN substring(module,1,1) IN ('%', '&') THEN substring(module, 2) ELSE module END as module_name,
         CASE WHEN substring(module,1,1) = '%' THEN 'flip-flop'
              WHEN substring(module,1,1) = '&' THEN 'conjunction'
              WHEN module = 'broadcaster' THEN 'broadcaster'
              ELSE 'untyped'
         END as module_type,
         destination
  FROM (
    SELECT row_number() over () as _row, split_part(line, ' -> ', 1) as module, split_part(line, ' -> ', 2) as destination 
    FROM lance_input
  ) t
),states AS (
  SELECT module_name, module_type, 'OFF' as status
  FROM origin
  WHERE module_type = 'flip-flop'

  UNION ALL

  SELECT a.module_name, a.module_type, string_agg(b.module_name || ':low', ',') as status
  FROM origin a, origin b
  WHERE a.module_type = 'conjunction'
  AND b.destination || ',' like '%' || a.module_name || ',%'
  GROUP BY a.module_name, a.module_type
) select module_name, status from states;

1000次发送

每当一次发送后,所有的状态都处理完毕后,需要进行下一次按钮发送。通过not exists的条件来进行判断,每当无后续信号时,则重新发送一次按钮。

    SELECT 'button' as current_module, 'low' as pulse, 'broadcaster' as next_module, b.button_seq + 1 as button_seq
    FROM (select 1) a, (select button_seq from signals_inner limit 1) b
    WHERE not exists (
      SELECT 1
      FROM signals_inner a
      JOIN origin b
      ON a.next_module = b.module_name
      AND a.pulse is not null
    ) 
    AND b.button_seq < 1000

完整SQL

WITH RECURSIVE origin AS (
  SELECT _row, CASE WHEN substring(module,1,1) IN ('%', '&') THEN substring(module, 2) ELSE module END as module_name,
         CASE WHEN substring(module,1,1) = '%' THEN 'flip-flop'
              WHEN substring(module,1,1) = '&' THEN 'conjunction'
              WHEN module = 'broadcaster' THEN 'broadcaster'
              ELSE 'untyped'
         END as module_type,
         destination
  FROM (
    SELECT row_number() over () as _row, split_part(line, ' -> ', 1) as module, split_part(line, ' -> ', 2) as destination 
    FROM lance_input
  ) t
),
signals AS (
  SELECT 'button' as current_module, 'low' as pulse, 'broadcaster' as next_module, 1 as button_seq

  UNION ALL

  SELECT *
  FROM (
    WITH signals_inner AS (
      SELECT * FROM signals
    )
    SELECT current_module, pulse, unnest(regexp_split_to_array(destination, ', ')) as next_module, button_seq
    FROM (
      SELECT b.module_name as current_module, 
             CASE WHEN b.module_type = 'broadcaster' THEN a.pulse
                  WHEN b.module_type = 'flip-flop' AND a.pulse = 'low' THEN flip(b.module_name)
                  WHEN b.module_type = 'conjunction' THEN conjunction(b.module_name, a.current_module, a.pulse)
             END as pulse,
             b.destination,
             button_seq
      FROM signals_inner a JOIN origin b
      ON a.next_module = b.module_name
      AND a.pulse is not null
    ) t
   
    UNION ALL

    SELECT 'button' as current_module, 'low' as pulse, 'broadcaster' as next_module, b.button_seq + 1 as button_seq
    FROM (select 1) a, (select button_seq from signals_inner limit 1) b
    WHERE not exists (
      SELECT 1
      FROM signals_inner a
      JOIN origin b
      ON a.next_module = b.module_name
      AND a.pulse is not null
    ) 
    AND b.button_seq < 1000
  ) t
)
SELECT pulse,count(*) from signals group by pulse;

Part Two

查找发送low信号到rx的最小按键次数

&mr -> qb
&rz -> qb
&kv -> qb
&jg -> qb
&qb -> rx

10000次的观察

qb发送low到rx,由于qb是conjunction类型,需要mr/rz/kv/jb四个module发送high到qb。

运行10000次后,看下四个module发送high信号的次数分布情况

postgres=# select * From lance_test where current_module = 'mr' and next_module = 'qb' and pulse = 'high';
 current_module | pulse | next_module | button_seq 
----------------+-------+-------------+------------
 mr             | high  | qb          |       8006
 mr             | high  | qb          |       4003
(2 rows)

postgres=# select * From lance_test where current_module = 'rz' and next_module = 'qb' and pulse = 'high';
 current_module | pulse | next_module | button_seq 
----------------+-------+-------------+------------
 rz             | high  | qb          |       4073
 rz             | high  | qb          |       8146
(2 rows)

postgres=# select * From lance_test where current_module = 'kv' and next_module = 'qb' and pulse = 'high';
 current_module | pulse | next_module | button_seq 
----------------+-------+-------------+------------
 kv             | high  | qb          |       7478
 kv             | high  | qb          |       3739
(2 rows)

postgres=# select * From lance_test where current_module = 'jg' and next_module = 'qb' and pulse = 'high';
 current_module | pulse | next_module | button_seq 
----------------+-------+-------------+------------
 jg             | high  | qb          |       3911
 jg             | high  | qb          |       7822
(2 rows)

则计算四个数字的最小公倍数即可。

次数的背后

网上找到了这篇文章,比较有意思,原来这个次数背后是有一定规则的。

以kv这个module为例,根据规则顺序画了具体的依赖图,发送信号到qz以红线标注

具体用二进制表示,就是1110 1001 1011,对应的十进制就是3739,正好是kv的按键次数。

因为flip模块交替发送high low信号,而遇到high信号不处理,因此越往后需要的次数就越多。比如这里lx需要2次,nt需要8次。

发表评论