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