initial commit
This commit is contained in:
commit
a74802b55e
3 changed files with 498 additions and 0 deletions
14
nine/package.json
Normal file
14
nine/package.json
Normal 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
341
nine/src/automaton.js
Normal 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
143
nine/src/index.js
Normal 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)
|
||||||
|
|
Loading…
Reference in a new issue