initial commit

This commit is contained in:
Rowan 2024-12-12 23:18:43 -06:00
commit a74802b55e
3 changed files with 498 additions and 0 deletions

14
nine/package.json Normal file
View file

@ -0,0 +1,14 @@
{
"name": "nine",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}

341
nine/src/automaton.js Normal file
View file

@ -0,0 +1,341 @@
const seek = value => () => value
export const Seek = Object.freeze({
Left: seek(-1),
Right: seek(1),
Stay: seek(0),
First: reader => -reader.position,
Last: reader => (reader.length - 1) - reader.position,
JumpFromStack: reader => {
const state = reader.pop()
reader.push()
return state.position - reader.position
},
SeekFromStack: consuming => reader => {
const position = (consuming ? reader.peek() : reader.pop()).position
return position - reader.position
}
})
export const Predicate = Object.freeze({
AnyOf: (...symbols) => b => symbols.includes(b.symbol),
Every: (...fns) => x => fns.every(fn => fn(x)),
Equals: a => b => a === b.symbol,
False: () => false,
InBounds: direction => reader => reader.inBounds(reader.position + direction()),
Not: fn => x => !fn(x),
Number: x => !isNaN(x.symbol),
OutOfBounds: direction => reader => !Predicate.InBounds(direction)(reader),
Some: (...fns) => x => fns.some(fn => fn(x)),
True: () => true,
})
const tee = fn => x => {
fn(x)
return x.symbol
}
export const Replace = Object.freeze({
Seq: (...fns) => x => fns.reduce((_acc, fn) => fn(x), x.symbol),
With: x => () => x,
Nothing: x => x.symbol,
Push: x => {
x.push()
return x.symbol
},
Pop: x => x.pop(),
Swap: x => {
const tmp = x.pop()
x.push()
x.push(tmp)
return tmp.symbol
},
ClearStack: tee(x => x.clear()),
FromStack: x => x.peek().symbol,
Tee: tee
})
class Head {
#position = 0
get position() { return this.#position }
left() { this.move(Left) }
right() { this.move(Right) }
move(value) { this.#position += value }
read(tape) {
return tape.at(this.#position)
}
write(tape, value) {
tape.write(this.#position, value)
}
}
export class Tape {
#value
constructor(input) {
this.#value = Array.isArray(input) ? input : input.split('')
}
get value() { return this.#value }
get length() { return this.#value.length }
at(n) {
return this.value[n]
}
write(n, value) {
this.#value[n] = value
}
}
export class Rule {
constructor(from, to, predicate, replaceSymbol, action) {
this.fromState = from
this.toState = to
this.predicate = predicate
this.replaceSymbol = replaceSymbol
this.action = action
}
static is(from, to, currentSymbol, nextSymbol, action) {
return new Rule(
from,
to,
Predicate.Equals(currentSymbol),
nextSymbol,
action
)
}
}
export class Rules {
#firstState
#rules = new Map()
constructor(rules) {
this.#firstState = rules[0]
this.#initialize(rules)
}
get firstState() { return this.#firstState }
#initialize(rules) {
rules.forEach(rule => {
const transition = this.#rules.get(rule.fromState)
if (transition) {
transition.push(rule)
} else {
this.#rules.set(rule.fromState, [rule])
}
})
}
get(state, reader) {
return this.#rules.get(state)?.find(rule => rule.predicate(reader)) ?? false
}
}
class ReaderState {
constructor(position, symbol) {
this.position = position
this.symbol = symbol
}
static from(reader) {
return new ReaderState(reader.position, reader.symbol)
}
}
class Iterator extends Array {
#index = 0
done() {
return this.length === 0 || this.#index === this.length - 1
}
peek() {
return this[this.#index]
}
next() {
return this[this.#index++]
}
take(n) {
const result = []
while (!this.done() && n--) {
result.push(this.next())
}
return result
}
skip(n) {
this.take(n)
return this
}
takeUntil(fn) {
const result = []
let c = 0
while (!this.done() && this.peek() && !fn(this.peek())) {
result.push(this.next())
c++
}
return result
}
skipUntil(fn) {
this.takeUntil(fn)
return this
}
}
export class Reader {
#head
#tape
#stack = []
constructor(head, tape) {
this.#head = head
this.#tape = tape
}
get head() { return this.#head }
get tape() { return this.#tape }
get stack() { return Iterator.from(this.#stack) }
get position() {
return this.#head.position
}
get length() {
return this.#tape.length
}
get symbol() {
return this.#head.read(this.#tape)
}
clear() {
this.#stack.length = 0
}
inBounds(index) {
return index >= 0 && index <= this.length - 1
}
push(x = ReaderState.from(this)) {
this.#stack.push(x)
}
pop() {
return this.#stack.pop()
}
peek() {
return this.#stack[this.#stack.length - 1]
}
}
const colors = {
brightWhite: [97, 107],
brightCyan: [96, 106],
brightMagenta: [95, 105],
brightBlue: [94, 104],
brightYellow: [93, 103],
brightGreen: [92, 102],
brightRed: [91, 101],
brightBlack: [90, 100],
white: [37, 47],
cyan: [36, 46],
magenta: [35, 45],
blue: [34, 44],
yellow: [33, 43],
green: [32, 42],
red: [31, 41],
black: [30, 40],
}
const color = (code, t) => {
if (Array.isArray(code)) {
return code.reduce((acc, c) => color(c, acc), t)
}
return `\x1b[${code}m${t}` + (t.endsWith(reset) ? '' : reset)
}
const reset = '\x1b[0m'
//const color = t => `\x1b[44m\x1b[93m${t}\x1b[0m`
const highlight = (i, arr, col = colors.cyan, spaces = 0) => {
const a = arr.slice()
const len = a[i].length
a[i] = color(col, a[i])
const diff = a[i].length - len
//console.log(a[i].length, len, diff, spaces)
a[i] = a[i].padEnd(spaces + diff)
return a
}
const range = n => [...Array(n).keys()]
export class TuringMachine {
#head = new Head()
#rules
#state
#reader
constructor(tape, rules, initialState = rules.firstState.fromState, debug = false) {
this.tape = tape
this.debug = debug
this.#rules = rules
this.#state = initialState
this.#reader = new Reader(this.#head, this.tape)
}
get state() { return this.#state }
get value() { return this.tape.value }
display(rule, next) {
console.log(this.#state, '->', rule.toState, '(', this.#reader.symbol, '->', next, ')')
const length = this.tape.length
const spaces = length.toString().length + 1
console.log(highlight(this.#reader.position, this.value, [1, 2, 4, colors.black[1], colors.yellow[0]], spaces).join(' '.repeat(spaces)))
const numbers = range(this.tape.length).map(String)
const highlighted = highlight(this.#reader.position, numbers, [1, 7], spaces + 3).map(x => x.padEnd(spaces + 1, ' ')).join('')
console.log(highlighted)
}
step(rule) {
const next = rule.replaceSymbol(this.#reader)
if (this.debug) {
this.display(rule, next)
}
if (next) {
this.#head.write(this.tape, next)
}
this.#state = rule.toState
this.#head.move(rule.action(this.#reader))
}
next() {
return this.#rules.get(this.#state, this.#reader)
}
exec() {
let next
while (next = this.next()) {
this.step(next)
}
}
}

143
nine/src/index.js Normal file
View file

@ -0,0 +1,143 @@
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { Rules, Rule, Predicate, Replace, Tape, TuringMachine, Seek } from './automaton.js'
const { First, Last, Left, Right, Stay, SeekFromStack, JumpFromStack } = Seek
const { ClearStack, Nothing, Push, Seq, Swap, With } = Replace
const { InBounds, Not, OutOfBounds, True } = Predicate
const State = Object.freeze({
Start: 'start',
ScanRight: 'scan-right',
FindFile: 'find-file',
ScanLeft: 'scan-left',
FindSpace: 'find-space',
ClearFile: 'clear-file',
})
const IsFreeSpace = Predicate.Equals('.')
const IsNotFree = Not(IsFreeSpace)
const IsFartherRight = x => x.peek().position <= x.position
const dot = ch => ch.symbol === '.'
const notDot = ch => !dot(ch)
const TrySkipFiles = x => {
const stack = x.stack.slice()
const a = stack.skipUntil(dot).takeUntil(notDot)
const skip = !stack.done()
//console.log(a, 'skipping?', skip, !stack.done())
return skip
}
const FirstOrStack = x => {
// unwind the stack
const states = []
let next
while (next = x.pop()) {
if (next.position < x.position) {
break
} else {
states.push(next)
next = null
}
}
// rewind it
states.reverse()
let tmp
while (tmp = states.pop()) {
x.push(tmp)
}
return next ? next : First(x)
//return x.peek() ? x.pop().position : First(x)
}
const TrySkipping = Seq(InBounds(Right), TrySkipFiles)
const HasMoreFiles = x => {
const stack = x.stack.slice()
stack.skipUntil(dot).skipUntil(notDot)
return !stack.done()
}
const ShouldContinue = Predicate.Every(OutOfBounds(Right), HasMoreFiles)
// | From | To | Condition | Next Character | Move Direction |
// | ------------------------------------------------------------------------------|
// new Rule('q0', 'q1', reader => reader.symbol === '1', (_reader) => '0', 1)
const rules = new Rules([
new Rule(State.Start, State.ScanRight, True, Nothing, Right),
new Rule(State.ScanRight, State.FindFile, TrySkipping, ClearStack, Last),
new Rule(State.ScanRight, State.FindFile, ShouldContinue, ClearStack, Stay),
new Rule(State.ScanRight, State.ScanRight, InBounds(Right), Push, Right),
new Rule(State.FindFile, State.ScanLeft, IsNotFree, Seq(ClearStack, Push), Stay),
new Rule(State.FindFile, State.FindFile, IsFreeSpace, Nothing, Left),
new Rule(State.ScanLeft, State.ScanLeft, InBounds(Left), Nothing, FirstOrStack),
new Rule(State.ScanLeft, State.FindSpace, OutOfBounds(Left), Nothing, Stay),
new Rule(State.FindSpace, State.ScanRight, IsFartherRight, ClearStack, Right),
new Rule(State.FindSpace, State.ClearFile, IsFreeSpace, Swap, JumpFromStack),
new Rule(State.FindSpace, State.FindSpace, InBounds(Right), Nothing, Right),
new Rule(State.ClearFile, State.ScanRight, True, With('.'), SeekFromStack(false)),
])
const char = x => String.fromCodePoint(x)
const toInt = x => x.codePointAt() - 32
const repeat = (n, value) => value.repeat(n)
const expand = input => {
return input.split('').reduce((acc, n, i) => {
if (i % 2) {
return acc.concat(repeat(n, char(46)))
} else {
return acc.concat(repeat(n, char((i / 2) + 32)))
}
}, '')
}
const compress = input => {
const tape = new Tape(input)
const tm = new TuringMachine(tape, rules)
tm.debug = false
tm.exec()
return tm.value
}
const notFree = ch => ch !== '.'
const mul = (a, b) => a * b
const add = (a, b) => a + b
const checksum = input => input.filter(notFree).map(toInt).map(mul).reduce(add)
const main = input => {
const expanded = expand(input)
console.log(expanded)
const compressed = compress(expanded)
console.log(compressed.filter(notFree).map(toInt))
const sum = checksum(compressed)
return sum
}
const tests = [
['2333133121414131402', 1928],
['1010101010101010101010', 385],
['111111111111111111111', 290],
['10101010101010101010101', 506],
['90909', 486]
]
tests.forEach(([input, expected]) => {
console.log(`input length is ${input.length}`)
const result = main(input)
console.log(result === expected, result)
})
//readFile(resolve('./input'), { encoding: 'utf8' })
// .then(x => {
// console.log(`input length is ${x.length}`)
// return main(x)
// })
// .then(console.log)