Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vVW0H-004cr1-23 for pgsql-general@arkaria.postgresql.org; Tue, 16 Dec 2025 14:26:18 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vVW0F-0072zV-2q for pgsql-general@arkaria.postgresql.org; Tue, 16 Dec 2025 14:26:16 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vVW0F-0072zK-1H for pgsql-general@lists.postgresql.org; Tue, 16 Dec 2025 14:26:16 +0000 Received: from mail-oa1-x34.google.com ([2001:4860:4864:20::34]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vVW0E-000ylJ-1b for pgsql-general@lists.postgresql.org; Tue, 16 Dec 2025 14:26:15 +0000 Received: by mail-oa1-x34.google.com with SMTP id 586e51a60fabf-3e80c483a13so3314200fac.2 for ; Tue, 16 Dec 2025 06:26:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1765895173; x=1766499973; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=+6rXSDCz60WWlerOketkgyKrls8+KCdluKyddMdBPn8=; b=kE1wB3XRx7sBqkkDGUx51kaZ1z1zXF/XhyfK8BpBYWtSb80Lu0Sty7tu70MTt3fs+4 FBVbZ5wraYbSuHPcyt1jgfuGADMHW6W3RRF4Hbsnu6lSfKUJWwNd+m2VOtD6coUIz3+i X8dJER+Ib2GPKafwPgyFf5d3qYU9g+k5EcKT72HKYlaEARvWxuZz/lxKsAwlomMaIzcM Kx1zX06AZyitU8R5jA9ySswT2PLaaKAOY+3rFkPFtjPnVGD81v5toftAVdsUxZO6E+Kd 5kIzwaBWVtBW0PtjqjI76e2MX0m1SLDUg60InQfSnnr5JvUYn0IOfRK/h5rdpPgvkyyj QN7Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1765895173; x=1766499973; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=+6rXSDCz60WWlerOketkgyKrls8+KCdluKyddMdBPn8=; b=kb2IrXe6bpoGfU09/SHPRmsrqBnFRW+DITjntSKMj565EGuwVXdZQ48m3z4qBJcsiP KkTMJRcGqDB8xUF+ibe3okijHeQrQ895q1OoN1wdBRgFQ+PTJYS+8ja9vlg+0/q9R3Fa Rh5Pvvh8svbgxnTLVFsO65xznI/+smWvax0yiM+Gq8vDoe7nCTnw9Cjx2MNfVH6pGU6x 92675aI4wH4QdxjeN+5sSgcOGObAOKbnZaWtPRiygsBpINip0r2HeLQuet9cMpQ8JZmp Y/Q07inrsp6dHqNI1E7ltvEvc3/ntrmHD31luqUc79TWX3hEj9U17GV+Thm8hV9j3fRB 4RXA== X-Gm-Message-State: AOJu0YzhhJOEXiisIDx3IiYMvuWSurJuEFss0v7Q8O0Q/5WXsaF0NNjg 68cfWEjNJYBQjDXwJ2XABus9mbVXM6VxeY86hseTt5b26nXMBYaDDdaU84XoatyQJRhzN5et9hY p64LMp8nxrqi5FUroRs/0OjW8HQ1BJ+8= X-Gm-Gg: AY/fxX4gtU0arAVZBkjTsCB2WVt+D/3UaiH7BfYBDThUFArHtOJ2XKhqGxHyhoBnpNZ ykk1ots9g+JEGgVLFWWU5muNHr6zrPHKz9sS20Aq28Y7vphpQM3MTxXprg1FAdrRNJ/PZgGvQpX uZI8LyyiRDMRwo5w6KNStvwPcPPh1P9vaIdUMm4LqOkK9VJTuPOXHvfgY05oG6/lvTtl0Jalrtq vqcymg5r0xfT2kzRMjBiKDjJzPV5caG6tafUpVd3FOXWjmgWRQnZWPjzxIK+cDL7uj40OSauM+U vvep4rYA6k+2WAg47ZBPNZXsCIW1qQacdNTjVQ== X-Google-Smtp-Source: AGHT+IEM8SqeeghVXpaez3PMdLnT7+DC2xzUV+JgiOZwJes6C3MFO0WsHXcPx6DTt8J+nkNiR9171UA6881rN7i5GEY= X-Received: by 2002:a05:6820:221f:b0:659:9a49:8e59 with SMTP id 006d021491bc7-65b45253199mr6003649eaf.41.1765895173288; Tue, 16 Dec 2025 06:26:13 -0800 (PST) MIME-Version: 1.0 References: In-Reply-To: From: Greg Sabino Mullane Date: Tue, 16 Dec 2025 09:25:36 -0500 X-Gm-Features: AQt7F2qA-SymRFrP02HJluvFa4hdd0L0m1ETPxpm_MairFS-zSFe9PSKreGgybg Message-ID: Subject: Re: Advent of Code Day 8 To: Bernice Southey Cc: pgsql-general@lists.postgresql.org Content-Type: multipart/alternative; boundary="00000000000083b6810646128297" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --00000000000083b6810646128297 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Tue, Dec 16, 2025 at 7:59=E2=80=AFAM Bernice Southey wrote: > Greg Sabino Mullane wrote: > > What's so wrong with brute force? :) > Yeah, a few more days of AoC changed my mind. > Doing things in SQL and/or plpgsql definitely presents a lot of challenges, especially in comparison to a "regular" programming language. Oftentimes the problems later in the month run the test data just fine, but the real solution takes way, way too long without doing some tricks and shortcuts. There are some cases where Postgres has the advantage when you can use things like range types, but for the most part, it's pain. :) > Good luck with day 10 part 2. That's the only one I gave up on after > discovering everyone was using solvers, or rolling their own. It's by > far the hardest, but one person found a brilliant way...don't forget abou= t > part 1. > Thanks, I hope to get to that one soon. As I said, I'm trying to solve them in a single statement. Recursive CTEs, CASE, and creative use of JSON can get you a long way. Here's my day 7, which runs slow compared to other languages, but runs as a single SQL statement and no plpgsql, and I think is a good solution: /* Greg Sabino Mullane for AOC 2025 Day 7 "Laboratories" */ /* Assumes data file is in /tmp/aoc_2026_day7.input */ \pset footer off \set ON_ERROR_STOP on SET client_min_messages =3D ERROR; CREATE EXTENSION IF NOT EXISTS file_fdw SCHEMA public; CREATE SERVER IF NOT EXISTS aoc2025 FOREIGN DATA WRAPPER file_fdw; DROP SCHEMA IF EXISTS aoc_2025_7 CASCADE; CREATE SCHEMA aoc_2025_7; SET search_path =3D aoc_2025_7, public; CREATE FOREIGN TABLE aoc_2025_day7 (line TEXT) SERVER aoc2025 OPTIONS (filename '/tmp/aoc_2025_day7.input'); --------------------------- -- AOC 2025 DAY 7 PART 1 -- --------------------------- \timing on WITH RECURSIVE dims AS (SELECT length(line) AS len FROM aoc_2025_day7 LIMIT 1) ,aoc AS (SELECT string_agg(replace(line, '.','o'),'') as line FROM aoc_2025_day7) ,start AS (SELECT regexp_replace(line, 'S(.{'||len-1||'}).', 'S\1B') AS line FROM aoc, dims) , rec AS ( SELECT (select len FROM dims) AS xlen, line FROM start UNION SELECT xlen, regexp_replace( regexp_replace(line ,'B(.{'||xlen-1||'})o', 'B\1B', 'g') /* extend the beam */ ,'B(.{'||xlen-2||'})(?:o\^o|B\^o|o\^B)', 'B\1B^B', 'g') /* split the beam */ /* We need that tri-state check because if we overwrite existing B^B, we won't do a nearby one! */ AS line FROM rec ) ,rec2 AS (SELECT row_number() over() AS r, line from rec) , winner AS (SELECT len, line FROM rec2, dims ORDER BY r DESC LIMIT 1) SELECT sum(regexp_count(right(line, -xx), '^B.{'||len-1||'}\^')) FROM winner, generate_series(1, (select length(line) from winner)) xx; -- Best time: 3976 ms --------------------------- -- AOC 2025 DAY 7 PART 2 -- --------------------------- WITH RECURSIVE dims AS (SELECT length(line) AS len FROM aoc_2025_day7 LIMIT 1) ,aoc AS (SELECT string_agg(line,'') as line FROM aoc_2025_day7) ,rec AS ( SELECT 0 AS off, line, 0 AS col, '{}'::jsonb AS score FROM aoc UNION SELECT off+1, line, CASE WHEN 0=3Doff%len THEN len ELSE off%len END, score || CASE /* If our current item item is the start, mark that column with a score of 1 */ WHEN substring(line from off for 1) =3D 'S' THEN jsonb_build_object('c'||1+col, 1) /* If our current item is a splitter, change score of the two new beams */ WHEN substring(line from off for 1) =3D '^' THEN jsonb_build_object( /* Set the score for the left beam (existing left + middle) */ 'c'||col, COALESCE( (score -> ('c'||col)::text)::bigint, 0) + (score -> ('c'||col+1)::text)::bigint /* Set the score for the right beam (existing right + middle) */ ,'c'||col+2, COALESCE( (score -> ('c'||col+2)::text)::bigint, 0) + (score -> ('c'||col+1)::text)::bigint /* Reset the score to zero for this column, as we split! */ ,'c'||col+1, 0 ) /* end of jsonb_build_object */ ELSE '{}'::jsonb END FROM rec, dims WHERE off < (select length(line) from aoc) ) ,lastscore AS (SELECT score FROM rec ORDER BY off DESC LIMIT 1) ,lastvals AS (SELECT (jsonb_each(score)).value::bigint AS jval FROM lastscore) SELECT SUM(jval) FROM lastvals; -- Best time: 3428 ms --00000000000083b6810646128297 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
On Tue, Dec 16, 2025 at 7:59=E2=80=AFAM B= ernice Southey <bernice.sou= they@gmail.com> wrote:
Greg Sabino Mull= ane <htamfids@gm= ail.com> wrote:
> What's so wrong with brute force? :)
Yeah, a few more days of AoC changed my mind.

Doing things in SQL and/or plpgsql=C2=A0definitely presents a lot of= challenges, especially in comparison to a "regular" programming = language. Oftentimes the problems later in the month run the test data just= fine, but the real solution takes way, way too long without doing some tri= cks and shortcuts. There are some cases where Postgres has the advantage wh= en you can use things like range types, but for the most part, it's pai= n. :)
=C2=A0
Good luck with day 10 part 2. That's the only one I gave up on after= discovering everyone was using solvers, or rolling their own. It's by<= br> far the hardest, but one person found a brilliant way...don't forget ab= out part 1.

Thanks, I hope to get to th= at one soon. As I said, I'm trying to solve them in a single statement.= Recursive CTEs, CASE, and creative use of JSON can get you a long way. Her= e's my day 7, which runs slow compared to other languages, but runs as = a single SQL statement and no plpgsql, and I think is a good solution:


/* Greg Sabino Mullane for AOC 2025 Day 7 "Laboratories" *= /

/* Assumes data file is in /tmp/aoc_2026_day7.input */

\pse= t footer off
\set ON_ERROR_STOP on

SET client_min_messages =3D ER= ROR;

CREATE EXTENSION IF NOT EXISTS file_fdw SCHEMA public;

C= REATE SERVER IF NOT EXISTS aoc2025 FOREIGN DATA WRAPPER file_fdw;

DR= OP SCHEMA IF EXISTS aoc_2025_7 CASCADE;

CREATE SCHEMA aoc_2025_7;
SET search_path =3D aoc_2025_7, public;

CREATE FOREIGN TABLE ao= c_2025_day7 (line TEXT)
=C2=A0 SERVER aoc2025 OPTIONS (filename '/tm= p/aoc_2025_day7.input');

---------------------------
-- AOC 2= 025 DAY 7 PART 1 --
---------------------------

\timing on
WITH RECURSIVE
dims AS (SELECT length(line) AS len FROM aoc_2025_day7 L= IMIT 1)
,aoc AS (SELECT string_agg(replace(line, '.','o'= ),'') as line FROM aoc_2025_day7)
,start AS (SELECT regexp_repla= ce(line, 'S(.{'||len-1||'}).', 'S\1B') AS line FROM= aoc, dims)
, rec AS (
=C2=A0 SELECT (select len FROM dims) AS xlen, = line FROM start
=C2=A0 UNION
=C2=A0 SELECT xlen,
regexp_replace(=C2=A0regexp_replace(line
=C2=A0 ,'B(.{'||xlen-1||'})o'= ;, 'B\1B', 'g')=C2=A0 =C2=A0 =C2=A0 =C2=A0/* extend the bea= m */
=C2=A0 ,'B(.{'||xlen-2||'})(?:o\^o|B\^o|o\^B)', = 9;B\1B^B', 'g')=C2=A0 /* split the beam */
=C2=A0 /* We need= that tri-state check because if we overwrite existing B^B, we won't do= a nearby one! */
=C2=A0AS line
FROM rec
)
,rec2 AS (SELECT row= _number() over() AS r, line from rec)
, winner AS (SELECT len, line FROM= rec2, dims ORDER BY r DESC LIMIT 1)
SELECT sum(regexp_count(right(line,= -xx), '^B.{'||len-1||'}\^')) FROM winner,
=C2=A0 gener= ate_series(1, (select length(line) from winner)) xx;

-- Best time: 3= 976 ms

---------------------------
-- AOC 2025 DAY 7 PART 2 -----------------------------

WITH RECURSIVE

=C2=A0dims AS (SE= LECT length(line) AS len FROM aoc_2025_day7 LIMIT 1)
,aoc =C2=A0AS (SELE= CT string_agg(line,'') as line FROM aoc_2025_day7)
,rec =C2=A0AS= (
SELECT 0 AS off, line, 0 AS col, '{}'::jsonb AS score FROM ao= c
UNION
SELECT off+1, line, CASE WHEN 0=3Doff%len THEN len ELSE off%l= en END,

=C2=A0 score ||
=C2=A0 CASE
=C2=A0 =C2=A0 /* If our c= urrent item item is the start, mark that column with a score of 1 */
=C2= =A0 =C2=A0 WHEN substring(line from off for 1) =3D 'S' THEN jsonb_b= uild_object('c'||1+col, 1)

=C2=A0 =C2=A0 /* If our current i= tem is a splitter, change score of the two new beams */
=C2=A0 =C2=A0 WH= EN substring(line from off for 1) =3D '^' THEN jsonb_build_object(<= br>
=C2=A0 =C2=A0 =C2=A0 /* Set the score for the left beam (existing le= ft + middle) */
=C2=A0 =C2=A0 =C2=A0 'c'||col,
=C2=A0 =C2=A0= =C2=A0 =C2=A0 COALESCE( (score -> ('c'||col)::text)::bigint, 0)=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 + (score -> ('c'||col+1)::text):= :bigint

=C2=A0 =C2=A0 =C2=A0 /* Set the score for the right beam (ex= isting right + middle) */
=C2=A0 =C2=A0 =C2=A0 ,'c'||col+2,
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 COALESCE( (score -> ('c'||col+2)::te= xt)::bigint, 0)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 + (score -> ('c'|= |col+1)::text)::bigint

=C2=A0 =C2=A0 =C2=A0 /* Reset the score to ze= ro for this column, as we split! */
=C2=A0 =C2=A0 =C2=A0 ,'c'||c= ol+1, 0
=C2=A0
=C2=A0 =C2=A0 ) /* end of jsonb_build_object */
=C2=A0 ELSE '{}'::jsonb END

FROM rec, dims
WHERE off &l= t; (select length(line) from aoc)
)
,lastscore AS (SELECT score FROM = rec ORDER BY off DESC LIMIT 1)
,lastvals AS (SELECT (jsonb_each(score)).= value::bigint AS jval FROM lastscore)
SELECT SUM(jval) FROM lastvals;=C2=A0 =C2=A0 =C2=A0
-- Best time: 3428 ms

<= /div> --00000000000083b6810646128297--