Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions interactive/examples/aoc2023/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gen/
74 changes: 74 additions & 0 deletions interactive/examples/aoc2023/GAPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Language gaps, from the AoC 2023 survey

Thirteen recurring gaps hit while expressing the 33 parts in this suite,
ranked by how many days each would have unlocked or simplified. They are
recorded together here rather than as thirteen issues: most are one-line
asks whose real content is the workaround they forced, and they rank
against each other better than they read alone. Each entry names what is
missing, how the programs coped, and the smallest change that would have
sufficed.

1. **Integer division family: `/`, `%`, shifts.** Worked around on four
days — enumeration fixpoints (day 6), parity folds (10p1), relational
modulo via a k-table cross join (15p1), a 40-step binary-halving gadget
(18) — and a named blocker for days 8, 23, 24. Minimal fix: `/` and `%`
BinOps. The highest value per implementation line in the survey.
2. **No `sum`/`max` reducers.** Every solved day paid this tax: sum is
`collect` + `fold` (materializing a list to add numbers), max is `min`
over `BIG - x` or a negate — the latter interacting badly with corgi's
encoding-order contract for negatives. Minimal fix: `sum` and `max`
beside `min` and `count`.
3. **`distinct` erases the value to unit**, so every iterative loop's state
must be packed into keys and re-projected around every join (days 3, 5,
10, 14, 16, 18, 19, 22). Minimal fix: a value-preserving idempotent
reducer — `distinct` on rows, or blessing `min` as the var-reducer idiom
(days 17 and 22 show it works).
4. **No scalar let-bindings or user scalar functions**; repeated
subexpressions are copy-pasted or staged through extra `map`s (days 1p2,
7, 18, 19 — day 19's projections had to be machine-generated). Minimal
fix: `let x = t in t` in the scalar grammar.
5. **No collection-level abstraction** (parameterized subgraphs): day
14p2's four roll directions and day 16's two beam programs are
copy-pasted pipelines. Minimal fix: named subgraph definitions with
collection parameters.
6. **No string/char literals**; all text logic is numeric charcode
comparison (days 1, 10, 13, 15, 16, 18, 19). Minimal fix: `'a'` char
literals, `"abc"` as list-of-codes sugar.
7. **`a - b` is multiset subtraction**; the natural "set minus" reading
silently corrupts counts when `b` has rows outside `a` (bit day 10;
latent everywhere). Idiom: `a - (a |> join b)`. Minimal fix: an `except`
operator.
8. **One `EDGES_FILE` round-robined across inputs** makes multi-relation
programs awkward; the tag+filter workaround (days 5, 19, 22) then runs
into corgi's arity-uniformity contract. Minimal fix: `EDGES_FILE_0`,
`EDGES_FILE_1`, ...
9. **No extrinsic iteration bound** (SQL's `RETURN AT RECURSION LIMIT`);
bounds must be encoded in the data. This is ergonomics, not expressive
power: a one-round stall (`var d = x;` then `x + (d | negate)` fires at
round 0 only) seeds a marker that marches `k -> k+1` under
`filter($0[0] < K)` with O(1) live rows per round — the full
delayed-pulse stepwise driver, in the language today. The residual ask
is sugar: an iteration cap on scopes, or a blessed pulse idiom in the
examples.
10. **No substring/window primitive**: k-char windows need k−1 self-joins
(day 1p2); line equality via collected lists (day 13). Minimal fix: a
windowing flatmap or list-slicing scalars.
11. **A reduce's List value cannot be re-projected as a single field**:
bare `$1` splices, `$1[0]` takes an element; idiom `if(1, $1, 0)`
(day 13). Minimal fix: a whole-value field form, e.g. `val($n)`.
12. **`&&` exists but `||` doesn't** (workaround `or(a,b)`); trivial
grammar fix (day 10).
13. **No non-integer arithmetic** (float/decimal, sqrt): the day 25
blocker; contributes to 6 and 24. Worth fixing only if DDIR wants
numeric-analysis workloads — everything else in AoC fits i64.

## Near-misses that are not gaps

Two things initially read as gaps and turned out not to be. The
delayed-pulse driver above (entry 9) is fully expressible via the `var`
stall — the survey's first pass got this wrong, and the construction is
worth knowing. And several days where SQL's stepwise idioms would not
transliterate ended up with *better* programs for it: day 14's closed-form
roll and day 22's interval-overlap fall are shorter, clearer, and more
incremental than their SQL counterparts. Absence of a feature is
occasionally load-bearing.
63 changes: 63 additions & 0 deletions interactive/examples/aoc2023/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# AoC 2023 in DDIR

A language-coverage and regression suite: 33 parts across 17 days of Advent
of Code 2023, expressed in pipe-form DDIR (`.ddp`) and checked against the
answers of Materialize's sqllogictest oracles
(`test/sqllogictest/advent-of-code/2023/`) — same inputs, same expected
answers, oracle quirks reproduced where needed to match.

Inputs are committed in their dense original text form (`dayNN/input.txt`,
the compact puzzle text as it appears in the slt oracles); `transcribe.py`
mechanically regenerates the i64 fact files the programs read (text →
`(line, pos, charcode)`, grids → `(row, col, cell)`, names → ids) into the
gitignored `gen/` directory at runtime. All puzzle logic lives in the
`.ddp` programs. `transcribe.py --pad` additionally writes day05's
arity-padded fact files for the corgi backend, which crashes on
mixed-arity inputs.

## Run

cargo build --release --example ddir
./run.sh # every part on the vec backend vs expected.txt; nonzero exit on any mismatch

`run.sh` first runs `transcribe.py` (python3) to regenerate `gen/`, then
runs each part as `EDGES_FILE=gen/<input> ddir --backend=vec
dayNN/partN.ddp <arity> 10 0 1 0`; the answer is the `Int` on the
`[partN]` inspect line.

`run.sh` asserts the **vec** backend only; corgi currently disagrees or
crashes on 4 of these parts (known open issues, minimized separately) and
is not asserted yet.

## Verdicts

CLEAN = direct expression; AWKWARD = right answer via a workaround idiom.

| day | p1 | p2 | note |
|-----|----|----|------|
| 01 | clean | awkward | no string literals: digit words matched as charcode filters over k-wide windows (k−1 self-joins) |
| 02 | clean | clean | anti-join as `games - bad`; max via `min` of (BIG − x) |
| 03 | awkward | awkward | run assembly needs 2 fixpoints; `distinct` erases values, so loop state is packed into keys |
| 04 | clean | clean | p2: card copies ARE collection multiplicities; the cascade is a bare `var` fixpoint |
| 05 | clean | awkward | stage-chain fixpoint, identity fallback = anti-join; p2 range split/holes all key-packed |
| 06 | awkward | — | no `/` or sqrt: hold times enumerated by fixpoint; slt has no separate part 2 |
| 07 | clean | awkward | hand type = pairwise-equality count; p2 stages maps to stand in for scalar lets |
| 10 | clean | awkward | loop = fixpoint, n/2 via parity fold; p2: `-` is multiset subtraction, set-minus needs an explicit anti-join |
| 11 | clean | clean | gap rows/cols by anti-join; L1 over all-pairs (slt input has no gaps, so p1 = p2) |
| 13 | awkward | clean | whole-line equality via collected Lists; p2 smudge = exactly one mismatched mirrored cell |
| 14 | clean | awkward | p1: closed-form roll, zero recursion; p2: round-tagged fixpoint with 4 hand-unrolled direction pipelines |
| 15 | awkward | clean | no `%`: modulo done relationally via a k-table cross join; p2 boxes need no sequencing |
| 16 | clean | clean | beam automaton with a literal transition table via flatmap |
| 17 | clean | clean | Bellman-Ford: `var mc = seeds + moves \| min`; p2 = 3 filter tweaks on p1 |
| 18 | awkward | awkward | shoelace prefix fixpoint; /2 via a 40-step binary-decomposition gadget; p2 hex decoded from charcodes |
| 19 | clean | clean | first-match = min (priority, next) inside the fixpoint; p2 reproduces the slt's negative-product quirk |
| 22 | clean | clean | interval overlap, no cell expansion; p2's count-inside-fixpoint converges fine |

Days 08, 09, 12, 20, 21, 23, 24 are absent because their slt files carry no
usable oracle (EXPLAIN-only, stubbed, or self-inconsistent); day 25's oracle
is an artifact of floating-point spectral iteration, which DDIR (integers
only) cannot chase.

The survey behind this suite also produced a ranked catalogue of 13
recurring language gaps (integer division/modulo, `max`/`sum` reducers,
value-preserving `distinct`, scalar lets, ...): see [GAPS.md](GAPS.md).
9 changes: 9 additions & 0 deletions interactive/examples/aoc2023/day01/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
noveneiner9
seventwoseven114
1two4two
hell0l1
79430242
159a951
0
seven2seven
h4mb5rg
18 changes: 18 additions & 0 deletions interactive/examples/aoc2023/day01/part1.ddp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- AoC 2023 day 1 part 1: per line, first digit * 10 + last digit; sum.
-- input 0: (line, pos, charcode)

let digits = input 0
| filter($0[2] >= 48 && $0[2] <= 57)
| key($0[0] ; $0[1], $0[2] - 48);
-- (line ; pos, digit)

let first = digits | min | map($0 ; $1[1]);
let last = digits | map($0 ; 0 - $1[0], $1[1]) | min | map($0 ; $1[1]);

let calib = first | join(last, ($0 ; $1[0] * 10 + $2[0]));
-- (line ; value)

export "result" = calib
| key(; $1[0]) | collect
| map(; fold($1, 0, ^1 + ^0[0]))
| arrange | inspect(part1);
43 changes: 43 additions & 0 deletions interactive/examples/aoc2023/day01/part2.ddp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- AoC 2023 day 1 part 2: digits and spelled-out digit words count; sum of
-- first*10+last per line. Words matched by charcode against windows built
-- from k successive self-joins (no substring primitive, no string literals).
-- input 0: (line, pos, charcode)

let chars = input 0 | key($0[0], $0[1] ; $0[2]);
-- (line, pos ; code)

let s2 = chars | map($0[0], $0[1] + 1 ; $0[1], $1[0])
| join(chars, ($0[0], $1[0] ; $1[1], $2[0]));
let s3 = s2 | map($0[0], $0[1] + 2 ; $0[1], $1[0], $1[1])
| join(chars, ($0[0], $1[0] ; $1[1], $1[2], $2[0]));
let s4 = s3 | map($0[0], $0[1] + 3 ; $0[1], $1[0], $1[1], $1[2])
| join(chars, ($0[0], $1[0] ; $1[1], $1[2], $1[3], $2[0]));
let s5 = s4 | map($0[0], $0[1] + 4 ; $0[1], $1[0], $1[1], $1[2], $1[3])
| join(chars, ($0[0], $1[0] ; $1[1], $1[2], $1[3], $1[4], $2[0]));
-- sk: (line, pos ; c0..c{k-1})

let digits = chars | filter($1[0] >= 48 && $1[0] <= 57)
| map($0[0] ; $0[1], $1[0] - 48);

let w_zero = s4 | filter($1[0]==122 && $1[1]==101 && $1[2]==114 && $1[3]==111) | map($0[0] ; $0[1], 0);
let w_one = s3 | filter($1[0]==111 && $1[1]==110 && $1[2]==101) | map($0[0] ; $0[1], 1);
let w_two = s3 | filter($1[0]==116 && $1[1]==119 && $1[2]==111) | map($0[0] ; $0[1], 2);
let w_three = s5 | filter($1[0]==116 && $1[1]==104 && $1[2]==114 && $1[3]==101 && $1[4]==101) | map($0[0] ; $0[1], 3);
let w_four = s4 | filter($1[0]==102 && $1[1]==111 && $1[2]==117 && $1[3]==114) | map($0[0] ; $0[1], 4);
let w_five = s4 | filter($1[0]==102 && $1[1]==105 && $1[2]==118 && $1[3]==101) | map($0[0] ; $0[1], 5);
let w_six = s3 | filter($1[0]==115 && $1[1]==105 && $1[2]==120) | map($0[0] ; $0[1], 6);
let w_seven = s5 | filter($1[0]==115 && $1[1]==101 && $1[2]==118 && $1[3]==101 && $1[4]==110) | map($0[0] ; $0[1], 7);
let w_eight = s5 | filter($1[0]==101 && $1[1]==105 && $1[2]==103 && $1[3]==104 && $1[4]==116) | map($0[0] ; $0[1], 8);
let w_nine = s4 | filter($1[0]==110 && $1[1]==105 && $1[2]==110 && $1[3]==101) | map($0[0] ; $0[1], 9);

let findings = digits + w_zero + w_one + w_two + w_three + w_four + w_five + w_six + w_seven + w_eight + w_nine;
-- (line ; pos, digit)

let first = findings | min | map($0 ; $1[1]);
let last = findings | map($0 ; 0 - $1[0], $1[1]) | min | map($0 ; $1[1]);
let calib = first | join(last, ($0 ; $1[0] * 10 + $2[0]));

export "result" = calib
| key(; $1[0]) | collect
| map(; fold($1, 0, ^1 + ^0[0]))
| arrange | inspect(part2);
5 changes: 5 additions & 0 deletions interactive/examples/aoc2023/day02/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Game 1: 1 red, 4 green, 4 blue; 3 red, 4 blue; 2 red, 1 green; 1 green, 1 red
Game 2: 20 green, 1 red; 1 blue, 4 red; 1 green, 1 red, 1 blue; 1 green, 1 red, 1 blue
Game 3: 8 red, 1 blue, 5 green; 9 red, 3 blue, 3 green; 3 green, 4 red
Game 4: 0 blue, 8 red, 4 green; 7 green, 8 blue, 9 red; 2 green, 5 red, 1 blue
Game 5: 20 red, 10 blue, 9 green; 2 blue, 3 red, 7 green; 1 green, 3 blue
13 changes: 13 additions & 0 deletions interactive/examples/aoc2023/day02/part1.ddp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- AoC 2023 day 2 part 1: sum of game ids where every set fits 13g/12r/14b.
-- input 0: (game, set, green, red, blue)

let games = input 0 | key($0[0] ;) | distinct;
let bad = input 0
| filter(not($0[2] <= 13 && $0[3] <= 12 && $0[4] <= 14))
| key($0[0] ;) | distinct;
let good = games - bad;

export "result" = good
| key(; $0[0]) | collect
| map(; fold($1, 0, ^1 + ^0[0]))
| arrange | inspect(part1);
16 changes: 16 additions & 0 deletions interactive/examples/aoc2023/day02/part2.ddp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- AoC 2023 day 2 part 2: per game, max needed of each color; sum of products.
-- max via min of (BIG - x), avoiding negatives.
-- input 0: (game, set, green, red, blue)

let maxg = input 0 | key($0[0] ; 1000000 - $0[2]) | min | map($0 ; 1000000 - $1[0]);
let maxr = input 0 | key($0[0] ; 1000000 - $0[3]) | min | map($0 ; 1000000 - $1[0]);
let maxb = input 0 | key($0[0] ; 1000000 - $0[4]) | min | map($0 ; 1000000 - $1[0]);

let power = maxg
| join(maxr, ($0 ; $1[0] * $2[0]))
| join(maxb, ($0 ; $1[0] * $2[0]));

export "result" = power
| key(; $1[0]) | collect
| map(; fold($1, 0, ^1 + ^0[0]))
| arrange | inspect(part2);
7 changes: 7 additions & 0 deletions interactive/examples/aoc2023/day03/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
...14...954......104...98..........11...222.........38.104....708..........................217..................330.................19..
.......@...................*...............................*.664........677................@....459.........187..........73.............
....41............178.....398....*...548..495..........983.........99.........282......409........*...........$.248...............165...
......261......300...............704.&.......*.......*........9.65..904.....6....*773....=.....680../511...2*.....=..99*....*..../......
..........200..............398.......22...100...........&...........10.......*.......73.....833...*...........*......300.............22.
..................@.100....*...........*...............*....19...300.....*.................@....954.......................200...........
.....-....&..@............828...........@268..844....534...................563.........409........$..........244.........722.286........
47 changes: 47 additions & 0 deletions interactive/examples/aoc2023/day03/part1.ddp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
-- AoC 2023 day 3 part 1: sum of numbers adjacent (8-way) to a symbol.
-- input 0: (row, col, charcode); '.' = 46, digits 48..57.
-- NB: `distinct` erases values, so loop state is packed into keys.

let cells = input 0 | filter($0[2] != 46) | key($0[0], $0[1] ; $0[2]);
let numerals = cells | filter($1[0] >= 48 && $1[0] <= 57) | map($0 ; $1[0] - 48);
let symbols = cells | filter(not($1[0] >= 48 && $1[0] <= 57));

-- run starts: digit cells with no digit to the left
let has_left = numerals | map($0[0], $0[1] + 1 ; $1[0]) | join(numerals, ($0 ; $2[0]));
let starts = numerals - has_left;

-- grow runs rightward: key (row, start, end_exclusive, value)
runs: {
let grow = run | map($0[0], $0[2] ; $0[1], $0[3])
| join(numerals, ($0[0], $1[0], $0[1] + 1, $1[1] * 10 + $2[0] ;));
var run = (starts | map($0[0], $0[1], $0[1] + 1, $1[0] ;)) + grow | distinct;
}
let extendable = runs::run | map($0[0], $0[2] ; $0[1], $0[3])
| join(numerals, ($0[0], $1[0], $0[1], $1[1] ;));
let maximal = runs::run - extendable;
-- key (row, start, end, value)

-- label each digit cell with its run start: key (row, col, start)
labels: {
let ext = label | map($0[0], $0[1] + 1 ; $0[2]) | join(numerals, ($0[0], $0[1], $1[0] ;));
var label = (starts | map($0[0], $0[1], $0[1] ;)) + ext | distinct;
}

-- digit cells adjacent to a symbol
let symnbrs = symbols
| flatmap(list(tuple(-1,-1), tuple(-1,0), tuple(-1,1),
tuple(0,-1), tuple(0,0), tuple(0,1),
tuple(1,-1), tuple(1,0), tuple(1,1)))
| map($0[0] + $1[1][0], $0[1] + $1[1][1] ;) | distinct;

let active_runs = labels::label
| map($0[0], $0[1] ; $0[2]) | join(symnbrs, ($0[0], $1[0] ;)) | distinct;
-- key (row, start) of runs adjacent to a symbol

let part_numbers = maximal | map($0[0], $0[1] ; $0[3]) | join(active_runs, ($0 ; $1[0]));
-- (row, start ; value)

export "result" = part_numbers
| key(; $1[0]) | collect
| map(; fold($1, 0, ^1 + ^0[0]))
| arrange | inspect(part1);
52 changes: 52 additions & 0 deletions interactive/examples/aoc2023/day03/part2.ddp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-- AoC 2023 day 3 part 2: gears = '*' adjacent to exactly two part numbers;
-- sum of the two numbers' product per gear.
-- input 0: (row, col, charcode)

let cells = input 0 | filter($0[2] != 46) | key($0[0], $0[1] ; $0[2]);
let numerals = cells | filter($1[0] >= 48 && $1[0] <= 57) | map($0 ; $1[0] - 48);
let gears = cells | filter($1[0] == 42);

let has_left = numerals | map($0[0], $0[1] + 1 ; $1[0]) | join(numerals, ($0 ; $2[0]));
let starts = numerals - has_left;

runs: {
let grow = run | map($0[0], $0[2] ; $0[1], $0[3])
| join(numerals, ($0[0], $1[0], $0[1] + 1, $1[1] * 10 + $2[0] ;));
var run = (starts | map($0[0], $0[1], $0[1] + 1, $1[0] ;)) + grow | distinct;
}
let extendable = runs::run | map($0[0], $0[2] ; $0[1], $0[3])
| join(numerals, ($0[0], $1[0], $0[1], $1[1] ;));
let maximal = runs::run - extendable;
let partvals = maximal | map($0[0], $0[1] ; $0[3]);
-- (row, start ; value)

labels: {
let ext = label | map($0[0], $0[1] + 1 ; $0[2]) | join(numerals, ($0[0], $0[1], $1[0] ;));
var label = (starts | map($0[0], $0[1], $0[1] ;)) + ext | distinct;
}
let labelcells = labels::label | map($0[0], $0[1] ; $0[2]);

let gearnbrs = gears
| flatmap(list(tuple(-1,-1), tuple(-1,0), tuple(-1,1),
tuple(0,-1), tuple(0,0), tuple(0,1),
tuple(1,-1), tuple(1,0), tuple(1,1)))
| map($0[0] + $1[1][0], $0[1] + $1[1][1] ; $0[0], $0[1]);
-- (nbr_cell ; gear)

let pairs = gearnbrs
| join(labelcells, ($1[0], $1[1], $0[0], $2[0] ;))
| distinct;
-- key (gear_r, gear_c, part_row, part_start)

let gearvals = pairs
| map($0[2], $0[3] ; $0[0], $0[1])
| join(partvals, ($1[0], $1[1] ; $2[0]));
-- (gear ; part value)

export "result" = gearvals
| collect
| filter(len($1) == 2)
| map(; $1[0][0] * $1[1][0])
| key(; $1[0]) | collect
| map(; fold($1, 0, ^1 + ^0[0]))
| arrange | inspect(part2);
10 changes: 10 additions & 0 deletions interactive/examples/aoc2023/day04/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Card 1: 91 58 89 8 19 64 92 28 22 1 | 6 94 21 70 81 59 5 35 24 31 43 69 91 12 51 53 98 50 70 98 47 6 9 49 50
Card 2: 49 56 57 80 28 9 3 19 55 6 | 35 25 76 45 35 73 12 93 29 23 50 33 75 36 4 33 90 84 1 9 44 62 99 80 85
Card 3: 97 29 93 95 66 40 97 9 58 11 | 22 56 90 13 40 84 83 70 65 80 73 84 58 93 98 79 46 51 47 70 8 50 43 70
Card 4: 62 79 90 45 63 70 75 26 14 92 | 70 5 69 58 80 64 72 4 36 24 40 76 79 16 79 11 80 88 49 92 15 24 5 49 22
Card 5: 54 26 80 65 14 46 77 59 12 20 | 96 89 95 25 19 22 34 9 24 86 87 63 16 31 5 22 91 71 8 80 33 2 65 67 78
Card 6: 22 10 58 44 5 97 97 57 88 8 | 54 50 79 45 2 40 90 30 82 37 29 99 50 90 51 84 97 62 8 4 89 82 86 59 65
Card 7: 65 94 76 4 41 40 1 6 50 96 | 82 90 42 92 22 18 29 96 47 91 71 2 5 3 42 73 45 26 15 13 29 37 7 63 81
Card 8: 73 19 52 43 47 54 6 86 12 34 | 25 70 26 15 35 10 65 81 48 72 98 48 18 94 8 34 6 44 79 25 77 27 78 61 28
Card 9: 32 51 38 86 17 56 42 4 67 38 | 55 5 26 91 98 11 52 1 48 13 55 95 60 15 16 51 54 22 91 8 26 70 26 35 92
Card 10: 6 40 74 5 31 63 1 5 12 64 | 88 7 91 54 4 62 37 66 5 69 59 78 17 47 61 2 6 56 36 59 2 71 63 87 72
13 changes: 13 additions & 0 deletions interactive/examples/aoc2023/day04/part1.ddp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- AoC 2023 day 4 part 1 (slt semantics): per card, numbers appearing >1
-- time across all 35 slots; points = 2^(k-1); sum.
-- input 0: (card, side, idx, value)

let dups = input 0 | key($0[0], $0[3] ;) | count | filter($1[0] > 1);
let points = dups
| map($0[0] ; $0[1]) | collect
| map($0 ; fold($1, 0, if(^1 == 0, 1, ^1 * 2)));

export "result" = points
| key(; $1[0]) | collect
| map(; fold($1, 0, ^1 + ^0[0]))
| arrange | inspect(part1);
Loading
Loading