Avatar

robsimmons

15 public vals
Joined December 9, 2023

SQLite Admin

This is a lightweight SQLite Admin interface to view and debug your SQLite data.

Screenshot 2023-12-08 at 13.35.04.gif

It's currently super limited (no pagination, editing data, data-type specific viewers), and is just a couple dozens lines of code over a couple different vals. Forks encouraged! Just comment on the val if you add any features that you want to share.

To use it on your own Val Town SQLite database, fork it to your account.

It uses basic authentication with your Val Town API Token as the password (leave the username field blank).

1
2
3
4
5
6
7
8
9
10
11
import { basicAuth } from "https://esm.town/v/pomdtr/basicAuth";
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import { html } from "https://esm.town/v/stevekrouse/html";
import { sqlite_admin_table } from "https://esm.town/v/stevekrouse/sqlite_admin_table";
import { sqlite_admin_tables } from "https://esm.town/v/stevekrouse/sqlite_admin_tables";
import { Hono } from "npm:hono@3.9.2";
const app = new Hono();
app.get("/", async (c) => c.html(await sqlite_admin_tables()));
app.get("/:table", async (c) => c.html(await sqlite_admin_table(c.req.param("table"))));
export default basicAuth(app.fetch);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import { Dusa } from "https://unpkg.com/dusa@0.0.11/lib/client.js";
const INPUT = `
...........
.....###.#.
.###.##..#.
..#.#...#..
....#.#....
.##..S####.
.##..#...#.
.......##..
.##.#.####.
.##..##.##.
...........
`
.trim()
.split("\n")
.map((line) => line.split(""));
const DISTANCE = 6;
const dusa = new Dusa(`
# AOC Day 21
#builtin NAT_SUCC s
# Input to start and node facts
start is (pair X Y) :-
field _ Y is Row,
field Row X is "S".
plot ".".
plot "S".
node (pair X Y) :-
field _ Y is Row,
field Row X is Ch,
plot Ch.
# Nodes to connected edges
edge (pair X Y) (pair X (s Y)) :-
node (pair X Y),
node (pair X (s Y)).
edge (pair X Y) (pair (s X) Y) :-
node (pair X Y),
node (pair (s X) Y).
edge A B :- edge B A.
# Locations reachable with N steps remaining
reachable D start :- distance is D.
reachable N B :- reachable (s N) A, edge A B.
`);
dusa.load(INPUT, "field");
dusa.assert({ name: "distance", args: [], value: DISTANCE });
console.log([...dusa.solution.lookup("reachable", 0)].length);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Dusa } from "https://unpkg.com/dusa@0.0.11/lib/client.js";
const INPUT = `
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7
`
.trim()
.split(',')
.map((line) => {
const label = line.split('-')[0].split('=')[0];
return {
label,
length: label.length,
ascii: label.split('').map((ch) => ch.charCodeAt(0)),
focal: line[label.length] === '-' ? null : parseInt(line.slice(label.length + 1)),
};
});
const dusa = new Dusa(`
# AOC Day 15, Part 2
#builtin INT_PLUS plus
#builtin INT_TIMES times
#builtin INT_MINUS minus
#builtin NAT_SUCC s
numBoxes is 256.
entries is N :- field _ "entries" is N.
labelLength Label is Len :-
field Ref "length" is Len,
field Ref "label" is Label.
ascii Label N is Ch :-
field _ Entry is Ref,
field Ref "label" is Label,
field Ref "ascii" is Str,
field Str N is Ch.
label Entry is Label :-
field _ Entry is Ref,
field Ref "label" is Label.
instruction Entry is del :-
field _ Entry is Ref,
field Ref "focal" is ().
instruction Entry is add N :-
field _ Entry is Ref,
field Ref "focal" is N, N != ().
# Hash computation
partialHash Label 0 is 0 :- ascii Label _ is _.
partialHash Label (s N) is Next :-
partialHash Label N is Val,
ascii Label N is Code,
X == times 17 (plus Val Code),
mod256 X is Next.
needMod256 X X :-
partialHash Label N is Val,
ascii Label N is Code,
X == times 17 (plus Val Code).
hash Label is Hash :-
partialHash Label (labelLength Label) is Hash.
# LOL, division by repeated subtraction
mod256 X is Y :-
needMod256 X Y,
Y < 256.
needMod256 X (minus Y 256) :-
needMod256 X Y,
Y >= 256.
instructionBox Box Entry Label Instr :-
label Entry is Label,
hash Label is Box,
instruction Entry is Instr.
`);
dusa.load({ data: INPUT, entries: INPUT.length }, 'field');
const instrs = Array.from({ length: 256 }).map((_, i) => []);
for (const [box, _entry, label, instr] of dusa.solution.lookup('instructionBox')) {
instrs[box].push({ label, instr });
}
const EVAL_PROGRAM = `
#builtin INT_PLUS plus
#builtin INT_TIMES times
#builtin INT_MINUS minus
#builtin NAT_SUCC s
contents 0 is nil.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import { Dusa } from "https://unpkg.com/dusa@0.0.11/lib/client.js";
const INPUT = `
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7
`
.trim()
.split(",")
.map((line) => ({ length: line.length, ascii: line.split("").map((ch) => ch.charCodeAt(0)) }));
const dusa = new Dusa(`
# AOC Day 15, Part 1
#builtin INT_PLUS plus
#builtin INT_TIMES times
#builtin INT_MINUS minus
#builtin NAT_SUCC s
entries is N :- field _ "entries" is N.
length Entry is Len :-
field _ Entry is Ref,
field Ref "length" is Len.
ascii Entry N is Ch :-
field _ Entry is Ref,
field Ref "ascii" is Str,
field Str N is Ch.
# Hash computation
partial Entry 0 is 0 :- ascii Entry _ is _.
partial Entry (s N) is Next :-
partial Entry N is Val,
ascii Entry N is Code,
X == times 17 (plus Val Code),
mod256 X is Next.
needMod256 X X :-
partial Entry N is Val,
ascii Entry N is Code,
X == times 17 (plus Val Code).
hash Entry is Hash :-
length Entry is Len,
partial Entry Len is Hash.
# LOL, division by repeated subtraction
mod256 X is Y :-
needMod256 X Y,
Y < 256.
needMod256 X (minus Y 256) :-
needMod256 X Y,
Y >= 256.
# Goal
sum 0 is 0.
sum (s N) is (plus Accum Hash) :-
sum N is Accum,
hash N is Hash.
goal is (sum entries).
`);
dusa.load({ data: INPUT, entries: INPUT.length }, "field");
console.log(dusa.solution.get("goal"));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Dusa } from "https://unpkg.com/dusa@0.0.10/lib/client.js";
const INPUT = `
O....#....
O.OO#....#
.....##...
OO.#O....O
.O.....O#.
O.#..O.#.#
..O..#O..O
.......O..
#....###..
#OO..#....
`
.trim()
.split("\n")
.map((line) => line.split(""));
const PROGRAM = `
# AOC Day 14, Part 2 - Take 1
#builtin INT_MINUS minus
#builtin NAT_SUCC s
#builtin INT_PLUS plus
input X Y is Ch :-
field Root "data" is Data,
field Data Y is Row,
field Row X is Ch.
height is N :- field Root "height" is N.
width is N :- field Root "width" is N.
# PART 1: RANGE CALCULATION
# We're going to calculate all the horizontal ranges
# (hRange (pair X Y) is Width) is Height) and vertical
# ranges (vRange (pair X Y), identifying them with
# their leftmost/topmost point. We'll also create
# a lookup table for every non-blocked point:
# (hRangeFor (pair X Y) is (pair XStart Y)) and
# (vRangeFor (pair X Y) is (pair X YStart)).
# A range (horizontal or vertical) starts on any open
# space (non-#) that is at the left/top boundary or
# to the immediate right/bottom of a # symbol
growHRange (pair 0 Y) 0 :-
input 0 Y is Ch, Ch != "#".
growHRange (pair X Y) X :-
input X Y is Ch, Ch != "#",
XPrev == minus X 1,
input XPrev Y is "#".
growVRange (pair X 0) 0 :-
input X 0 is Ch, Ch != "#".
growVRange (pair X Y) Y :-
input X Y is Ch, Ch != "#",
YPrev == minus Y 1,
input X YPrev is "#".
# We grow a range whenever the next step is non-blocked
growHRange (pair XStart Y) (s X) :-
growHRange (pair XStart Y) X,
input X Y is Ch, Ch != "#".
hRangeFor (pair X Y) is (pair XStart Y) :-
growHRange (pair XStart Y) X,
input X Y is Ch, Ch != "#".
growVRange (pair X YStart) (s Y) :-
growVRange (pair X YStart) Y,
input X Y is Ch, Ch != "#".
vRangeFor (pair X Y) is (pair X YStart) :-
growVRange (pair X YStart) Y,
input X Y is Ch, Ch != "#".
# We've found the range when we get to a blocked point
# or when we get to the end.
hRange (pair X Y) is (minus XEnd X) :-
growHRange (pair X Y) XEnd,
input XEnd Y is "#".
hRange (pair X Y) is (minus width X) :-
growHRange (pair X Y) width.
vRange (pair X Y) is (minus YEnd Y) :-
growVRange (pair X Y) YEnd,
input X YEnd is "#".
vRange (pair X Y) is (minus height Y) :-
growVRange (pair X Y) height.
# PART 2: NEXT-STEP POSITION CALCULATION
# char (dir N) X Y is Ch is the key output of this calculation,
# and it means that after the Nth time you slide rocks in direction
# dir (for dir in { up, down, left, right }), the character at
# position (X, Y) will be Ch. We start with having slid everything
# to the right zero times, since right is the end of our
# up/left/down/right spin cycle
char (right 0) X Y is Ch :- input X Y is Ch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Dusa } from "https://unpkg.com/dusa@0.0.10/lib/client.js";
const INPUT = `
O....#....
O.OO#....#
.....##...
OO.#O....O
.O.....O#.
O.#..O.#.#
..O..#O..O
.......O..
#....###..
#OO..#....
`
.trim()
.split("\n")
.map((line) => line.split(""));
const dusa = new Dusa(`
# AOC Day 14, Part 2 - Take 1
#builtin INT_MINUS minus
#builtin NAT_SUCC s
#builtin INT_PLUS plus
input X Y is Ch :-
field Root "data" is Data,
field Data Y is Row,
field Row X is Ch.
height is N :- field Root "height" is N.
width is N :- field Root "width" is N.
# PART 1: RANGE CALCULATION
# We're going to calculate all the horizontal ranges
# (hRange (pair X Y) is Width) is Height) and vertical
# ranges (vRange (pair X Y), identifying them with
# their leftmost/topmost point. We'll also create
# a lookup table for every non-blocked point:
# (hRangeFor (pair X Y) is (pair XStart Y)) and
# (vRangeFor (pair X Y) is (pair X YStart)).
# A range (horizontal or vertical) starts on any open
# space (non-#) that is at the left/top boundary or
# to the immediate right/bottom of a # symbol
growHRange (pair 0 Y) 0 :-
input 0 Y is Ch, Ch != "#".
growHRange (pair X Y) X :-
input X Y is Ch, Ch != "#",
XPrev == minus X 1,
input XPrev Y is "#".
growVRange (pair X 0) 0 :-
input X 0 is Ch, Ch != "#".
growVRange (pair X Y) Y :-
input X Y is Ch, Ch != "#",
YPrev == minus Y 1,
input X YPrev is "#".
# We grow a range whenever the next step is non-blocked
growHRange (pair XStart Y) (s X) :-
growHRange (pair XStart Y) X,
input X Y is Ch, Ch != "#".
hRangeFor (pair X Y) is (pair XStart Y) :-
growHRange (pair XStart Y) X,
input X Y is Ch, Ch != "#".
growVRange (pair X YStart) (s Y) :-
growVRange (pair X YStart) Y,
input X Y is Ch, Ch != "#".
vRangeFor (pair X Y) is (pair X YStart) :-
growVRange (pair X YStart) Y,
input X Y is Ch, Ch != "#".
# We've found the range when we get to a blocked point
# or when we get to the end.
hRange (pair X Y) is (minus XEnd X) :-
growHRange (pair X Y) XEnd,
input XEnd Y is "#".
hRange (pair X Y) is (minus width X) :-
growHRange (pair X Y) width.
vRange (pair X Y) is (minus YEnd Y) :-
growVRange (pair X Y) YEnd,
input X YEnd is "#".
vRange (pair X Y) is (minus height Y) :-
growVRange (pair X Y) height.
# PART 2: NEXT-STEP POSITION CALCULATION
# char (dir N) X Y is Ch is the key output of this calculation,
# and it means that after the Nth time you slide rocks in direction
# dir (for dir in { up, down, left, right }), the character at
# position (X, Y) will be Ch. We start with having slid everything
# to the right zero times, since right is the end of our
# up/left/down/right spin cycle
char (right 0) X Y is Ch :- input X Y is Ch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Dusa } from "https://unpkg.com/dusa@0.0.10/lib/client.js";
const INPUT = `
#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.
#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#
`
.trim()
.split("\n\n")
.map((grid) => grid.split("\n").map((line) => line.split("")));
const PROGRAM = `
# AOC Day 13 Solution
#builtin NAT_SUCC s
#builtin INT_MINUS minus
#builtin INT_PLUS plus
#builtin INT_TIMES times
input (s X) (s Y) is Ch :-
field Root Y is Row,
field Row X is Ch.
# Characters for Part 1 are exactly the inputs
char part1 X Y is Ch :- input X Y is Ch.
# Characters for Part 2 are the inputs with
# one smudge - a "#" turned to a "." (could
# symmetrically be a "." turned to a "#")
smudge is { pair X Y? } :-
input X Y is "#",
finishedPart1.
char part2 X Y is Ch :-
smudge is (pair SX SY),
input X Y is Ch,
SX != X.
char part2 X Y is Ch :-
smudge is (pair SX SY),
input X Y is Ch,
SY != Y.
char part2 X Y is "." :-
smudge is (pair X Y).
# finishedPart1 is a hack to make sure we only pick the
# smudge location after we've solved Part 1
finishedPart1 :- reflectVerticalLine part1 is Axis.
finishedPart1 :- reflectHorizontalLine part1 is Axis.
# Each part admits exactly one horizontal or vertical reflection
reflection part1 is { horizontal, vertical }.
reflection part2 is { horizontal, vertical }.
# The way the problem is set up, every row/column EXCEPT the last
# one is a valid candidate for reflection
reflectHorizontalLine Part is { Y? } :-
reflection Part is horizontal,
char Part _ Y is _,
char Part _ (s Y) is _.
reflectVerticalLine Part is { X? } :-
reflection Part is vertical,
char Part X _ is _,
char Part (s X) _ is _.
# The way we reflect is that we actually assert that the reflected
# image to the left/top of the mirror exists on top of the
# right/bottom part. In any case where the mirror is incorrectly
# placed, this will result in a contradiction and invalidate the
# solution.
char Part X Reflection is Ch :-
reflectHorizontalLine Part is Axis,
char Part X Y is Ch,
Y <= Axis,
Reflection == plus (s Axis) (minus Axis Y).
char Part Reflection Y is Ch :-
reflectVerticalLine Part is Axis,
char Part X Y is Ch,
X <= Axis,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { Dusa } from "https://unpkg.com/dusa@0.0.10/lib/client.js";
const INPUT = `
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1
`
.trim()
.split("\n");
const PROGRAM = `
#builtin NAT_SUCC s
#builtin STRING_CONCAT concat
value N is "#" :- char N is "#".
value N is "." :- char N is ".".
value N is { "#", "." } :- char N is "?".
startRun 0 is true :- value 0 is "#".
startRun (s N) is true :-
value N is ".",
value (s N) is "#".
startRun N is false :- value N is ".".
startRun (s N) is false :- value N is "#", value (s N) is "#".
startRun N is { false? } :- char N is _.
scan 0 is Lens :- runs is Lens.
scan (s N) is Lens :-
startRun N is false,
scan N is Lens.
scan (s N) is Lens :-
startRun N is true,
scan N is (cons Len Lens).
runAt N is Len :-
startRun N is true,
scan N is (cons Len Lens).
value N is "#" :-
runAt N is (s Len).
runAt (s N) is Len :-
runAt N is (s Len).
value N is "." :-
runAt N is 0.
#forbid startRun N is true, scan N is nil.
#forbid scan length is cons _ _.
#forbid length is Len, runAt Len is (s _).
result 0 is "".
result (s N) is (concat Accum Ch) :-
result N is Accum,
value N is Ch.
resolution is Str :- length is Len, result Len is Str.
`;
let accum = 0;
for (const line of INPUT) {
const [str, runstr] = line.split(" ");
const chars = str.split("");
const runs = runstr.split(",").map((n) => parseInt(n));
let dusalist = { name: "nil" };
for (let i = runs.length - 1; i >= 0; i--) {
dusalist = { name: "cons", args: [runs[i], dusalist] };
}
const dusa = new Dusa(PROGRAM);
dusa.assert(...chars.map((ch, i) => ({ name: "char", args: [i], value: ch })));
dusa.assert({ name: "length", args: [], value: chars.length });
dusa.assert({ name: "runs", args: [], value: dusalist });
let count = 0;
for (const solution of dusa.solutions) {
console.log(`${str} ${[...solution.lookup("resolution")][0][0]} ${runstr}`);
count += 1;
}
console.log(count);
accum += count;
}
console.log(accum);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Dusa } from "https://unpkg.com/dusa@0.0.10/lib/client.js";
const INPUT = `
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1
`
.trim()
.split("\n");
const PROGRAM = `
#builtin NAT_SUCC s
#builtin INT_PLUS plus
getWays 0 noRun RunList :- runs is RunList.
# When we're NOT in a run
numWays N noRun nil is 0 :-
getWays N noRun nil,
char N is "#".
numWays N noRun RunList is NWays :-
getWays N noRun RunList,
RunList == (cons (s Len) SubList),
char N is "#",
numWays (s N) (run Len) SubList is NWays.
getWays (s N) (run Len) SubList :-
getWays N noRun RunList,
RunList == (cons (s Len) SubList),
char N is Ch, Ch != ".".
numWays N noRun RunList is NWays :-
getWays N noRun RunList,
char N is ".",
numWays (s N) noRun RunList is NWays.
getWays (s N) noRun RunList :-
getWays N noRun RunList,
char N is Ch, Ch != "#".
numWays N noRun nil is NWaysDot :-
getWays N noRun nil,
char N is "?",
numWays (s N) noRun nil is NWaysDot.
numWays N noRun RunList is (plus NWaysDot NWaysHash) :-
getWays N noRun RunList,
char N is "?",
numWays (s N) noRun RunList is NWaysDot,
RunList == (cons (s Len) SubList),
numWays (s N) (run Len) SubList is NWaysHash.
# End of a run
numWays N (run 0) RunList is 0 :-
getWays N (run 0) RunList,
char N is "#".
numWays N (run 0) RunList is NWays :-
getWays N (run 0) RunList,
char N is Ch, Ch != "#",
numWays (s N) noRun RunList is NWays.
getWays (s N) noRun RunList :-
getWays N (run 0) RunList,
char N is Ch, Ch != "#".
# Middle of a run
numWays N (run (s Len)) RunList is 0 :-
getWays N (run (s Len)) RunList,
char N is ".".
numWays N (run (s Len)) RunList is NWays :-
getWays N (run (s Len)) RunList,
char N is Ch, Ch != ".",
numWays (s N) (run Len) RunList is NWays.
getWays (s N) (run Len) RunList :-
char N is Ch, Ch != ".",
getWays N (run (s Len)) RunList.
# End of the road
numWays N State (cons Len RunList) is 0 :-
getWays N State (cons Len RunList),
length is N.
numWays N (run (s Len)) RunList is 0 :-
getWays N (run (s Len)) RunList,
length is N.
numWays N noRun nil is 1 :-
getWays N noRun nil,
length is N.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import { Dusa } from "https://unpkg.com/dusa@0.0.10/lib/client.js";
const INPUT = `
...#......
.......#..
#.........
..........
......#...
.#........
.........#
..........
.......#..
#...#.....
`
.trim()
.split("\n")
.map((line) => line.trim().split(""));
const PROGRAM = `
# AOC Day 11
#builtin NAT_SUCC s
#builtin INT_TIMES times
#builtin INT_PLUS plus
#builtin INT_MINUS minus
# Process input to get X, Y coordinates
charAt X Y is Ch :-
field _ "map" is List,
field List Y is Cols,
field Cols X is Ch.
width is W :- field _ "width" is W.
expansionFactor is F :- field _ "expansionFactor" is F.
# Use default reasoning to find out which rows/cols have stars
hasStar y Y is { false? } :- charAt _ Y is _.
hasStar y Y is true :- charAt _ Y is "#".
hasStar x X is { false? } :- charAt X _ is _.
hasStar x X is true :- charAt X _ is "#".
needsRemap x 0 0.
needsRemap y 0 0.
remap Axis Old is (minus (plus New expansionFactor) 1) :-
needsRemap Axis Old New,
hasStar Axis Old is false.
remap Axis Old is New :-
needsRemap Axis Old New,
hasStar Axis Old is true.
needsRemap Axis (s Old) (s New) :-
remap Axis Old is New.
starAt (remap x X) (remap y Y) :-
charAt X Y is "#".
`;
function bigabs(x) {
return x < 0 ? -x : x;
}
for (const expansionFactor of [2, 10, 100, 1000000]) {
const dusa = new Dusa(PROGRAM);
dusa.load({ map: INPUT, width: INPUT[0].length, expansionFactor }, "field");
const solution = dusa.sample();
const stars = [...solution.lookup("starAt")];
let accum = 0n;
for (const [index, [x1, y1]] of stars.entries()) {
for (const [x2, y2] of stars.slice(index + 1)) {
accum += bigabs(x1 - x2) + bigabs(y1 - y2);
}
}
console.log(accum);
}