1283 lines
28 KiB
JavaScript
1283 lines
28 KiB
JavaScript
// TODO:
|
|
// - copy method
|
|
// - remove range method
|
|
|
|
DLLNode = class DLLNode {
|
|
constructor(val, skip = this, prev = this, next = this) {
|
|
this.val = val
|
|
this.skip = skip
|
|
this.prev = prev
|
|
this.next = next
|
|
}
|
|
|
|
adv(n = 1) {
|
|
let node = this == this.skip ? this.next : this
|
|
|
|
if (n < 0) {
|
|
while (n++) {
|
|
node = node.prev
|
|
if (node == this.skip) node = node.prev
|
|
}
|
|
} else {
|
|
while (n--) {
|
|
node = node.next
|
|
if (node == this.skip) node = node.next
|
|
}
|
|
}
|
|
|
|
return node
|
|
}
|
|
}
|
|
|
|
DLL = class DLL {
|
|
constructor(a = []) {
|
|
this.h = new DLLNode()
|
|
this.length = 0;
|
|
|
|
a.forEach((e, i, a) => this.insEnd(e))
|
|
}
|
|
|
|
insNodeAheadNode(old, node) {
|
|
node.next = old.next
|
|
old.next.prev = node
|
|
old.next = node
|
|
node.prev = old
|
|
return ++this.length
|
|
}
|
|
|
|
insDLLAheadNode(old, dll) {
|
|
let start = dll.getNode(0)
|
|
let end = dll.getNode(-1)
|
|
end.next = old.next
|
|
old.next.prev = end
|
|
old.next = start
|
|
start.prev = old
|
|
return this.length += dll.length
|
|
}
|
|
|
|
insAheadNode(old, val) {
|
|
let node = new DLLNode(val, this.h, old, old.next)
|
|
old.next = node
|
|
old.next.next.prev = node
|
|
return ++this.length
|
|
}
|
|
|
|
insStart(val) { return this.insAheadNode(this.h, val) }
|
|
unshift(val) { return this.insStart(val) }
|
|
|
|
insNodeBehindNode(old, node) {
|
|
node.prev = old.prev
|
|
old.prev.next = node
|
|
old.prev = node
|
|
node.next = old
|
|
return ++this.length
|
|
}
|
|
|
|
insDLLBehindNode(old, dll) {
|
|
let start = dll.getNode(0)
|
|
let end = dll.getNode(-1)
|
|
start.prev = old.prev
|
|
old.prev.next = start
|
|
old.prev = end
|
|
end.next = old
|
|
return this.length += dll.length
|
|
}
|
|
|
|
insBehindNode(old, val) {
|
|
let node = new DLLNode(val, this.h, old.prev, old)
|
|
old.prev = node
|
|
old.prev.prev.next = node
|
|
return ++this.length
|
|
}
|
|
|
|
insEnd(val) { return this.insBehindNode(this.h, val) }
|
|
|
|
push(...vals) {
|
|
let ret
|
|
vals.forEach((val) => ret = this.insEnd(val))
|
|
return ret
|
|
}
|
|
|
|
insNode(idx, node) { return this.insNodeBehindNode(this.getNode(idx), node) }
|
|
insDLL(idx, dll) { return this.insDLLBehindNode(this.getNode(idx), dll) }
|
|
ins(idx, val) { return this.insBehindNode(this.getNode(idx), val) }
|
|
insNodeAhead(idx, node) { return this.insNodeAheadNode(this.getNode(idx), node) }
|
|
insDLLAhead(idx, dll) { return this.insDLLAheadNode(this.getNode(idx), dll) }
|
|
insAhead(idx, val) { return this.insAheadNode(this.getNode(idx), val) }
|
|
|
|
removeNode(node) {
|
|
let tmp = node.next
|
|
node.prev.next = node.next
|
|
tmp.prev = node.prev
|
|
this.length--
|
|
return node
|
|
}
|
|
|
|
remove(idx) { return this.removeNode(this.getNode(idx)).val }
|
|
|
|
getNode(idx) { return this.h.adv(idx) }
|
|
get(idx) { return this.getNode(idx).val }
|
|
|
|
reverse() {
|
|
let node = this.h
|
|
|
|
do {
|
|
let next = node.next
|
|
let tmp = node.prev
|
|
node.prev = node.next
|
|
node.next = tmp
|
|
node = next
|
|
} while (node != this.h)
|
|
|
|
return this
|
|
}
|
|
|
|
rotate(rot) {
|
|
let tmp = this.h.next
|
|
this.removeNode(this.h)
|
|
this.insBehindNodeNode(tmp.adv(-rot), this.h)
|
|
}
|
|
|
|
forEachNode(f) {
|
|
let i = 0, node = this.h.next;
|
|
|
|
while (node != this.h) {
|
|
f(node, i++)
|
|
node = node.next
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
forEach(f) { return this.forEachNode((node, idx) => f(node.val, idx)) }
|
|
mapMut(f) { return this.forEachNode((node, idx) => node.val = f(node.val, idx)) }
|
|
|
|
includes(val) {
|
|
let res = false
|
|
this.forEach((e) => res |= e == val)
|
|
return res
|
|
}
|
|
|
|
toArray() {
|
|
let arr = new Array(this.length)
|
|
|
|
this.forEach((el, idx) => arr[idx] = el)
|
|
|
|
return arr
|
|
}
|
|
|
|
toJSON() { return this.toArray() }
|
|
toString() { return this.toArray().toString() }
|
|
}
|
|
|
|
Pt = Point = class Point {
|
|
constructor(x, y, z) {
|
|
this.is3D = z != undefined
|
|
this.x = x
|
|
this.y = y
|
|
this.z = z
|
|
}
|
|
|
|
equals(pt) { return this.x == pt.x && this.y == pt.y && (!this.is3D || this.z == pt.z) }
|
|
|
|
isIn(arr) { return this.indexIn(arr) != -1 }
|
|
indexIn(arr) { return arr.findIndex((pt) => this.equals(pt)) }
|
|
lastIndexIn(arr) { return arr.findLastIndex((pt) => this.equals(pt)) }
|
|
|
|
up() { return new Point(this.x, this.y - 1) }
|
|
down() { return new Point(this.x, this.y + 1) }
|
|
left() { return new Point(this.x - 1, this.y) }
|
|
right() { return new Point(this.x + 1, this.y) }
|
|
upleft() { return new Point(this.x - 1, this.y - 1) }
|
|
upright() { return new Point(this.x + 1, this.y - 1) }
|
|
downleft() { return new Point(this.x - 1, this.y + 1) }
|
|
downright() { return new Point(this.x + 1, this.y + 1) }
|
|
above() { return new Point(this.x, this.y, this.z - 1) }
|
|
below() { return new Point(this.x, this.y, this.z + 1) }
|
|
|
|
u() { return this.up() }
|
|
d() { return this.down() }
|
|
l() { return this.left() }
|
|
r() { return this.right() }
|
|
ul() { return this.upleft() }
|
|
ur() { return this.upright() }
|
|
dl() { return this.downleft() }
|
|
dr() { return this.downright() }
|
|
|
|
getUnfilteredAdjNeighborsIncSelf() {
|
|
if (!this.is3D) {
|
|
return new PointArray(
|
|
this.u(),
|
|
this.l(),
|
|
this.copy(),
|
|
this.r(),
|
|
this.d())
|
|
} else {
|
|
return new PointArray(
|
|
this.above(),
|
|
this.u(),
|
|
this.l(),
|
|
this.copy(),
|
|
this.r(),
|
|
this.d(),
|
|
this.below())
|
|
}
|
|
}
|
|
|
|
getUnfilteredWingNeighborsIncSelf() {
|
|
if (!this.is3D) {
|
|
console.error("Can't get wing neighbors of 2D point")
|
|
}
|
|
|
|
return new PointArray(
|
|
this.u().above(),
|
|
this.l().above(),
|
|
this.r().above(),
|
|
this.d().above(),
|
|
this.ul(),
|
|
this.ur(),
|
|
this.copy(),
|
|
this.dl(),
|
|
this.dr(),
|
|
this.u().below(),
|
|
this.l().below(),
|
|
this.r().below(),
|
|
this.d().below())
|
|
}
|
|
|
|
getUnfilteredDiagNeighborsIncSelf() {
|
|
if (!this.is3D) {
|
|
return new PointArray(
|
|
this.ul(),
|
|
this.ur(),
|
|
this.copy(),
|
|
this.dl(),
|
|
this.dr())
|
|
} else {
|
|
return new PointArray(
|
|
this.ul().above(),
|
|
this.ur().above(),
|
|
this.dl().above(),
|
|
this.dr().above(),
|
|
this.copy(),
|
|
this.ul().below(),
|
|
this.ur().below(),
|
|
this.dl().below(),
|
|
this.dr().below())
|
|
}
|
|
}
|
|
|
|
getUnfilteredAllNeighborsIncSelf() {
|
|
if (!this.is3D) {
|
|
return new PointArray(
|
|
this.ul(),
|
|
this.u(),
|
|
this.ur(),
|
|
this.l(),
|
|
this.copy(),
|
|
this.r(),
|
|
this.dl(),
|
|
this.d(),
|
|
this.dr())
|
|
} else {
|
|
return new PointArray(
|
|
this.ul().above(),
|
|
this.u().above(),
|
|
this.ur().above(),
|
|
this.l().above(),
|
|
this.above(),
|
|
this.r().above(),
|
|
this.dl().above(),
|
|
this.d().above(),
|
|
this.dr().above(),
|
|
this.ul(),
|
|
this.u(),
|
|
this.ur(),
|
|
this.l(),
|
|
this.copy(),
|
|
this.r(),
|
|
this.dl(),
|
|
this.d(),
|
|
this.dr(),
|
|
this.ul().below(),
|
|
this.u().below(),
|
|
this.ur().below(),
|
|
this.l().below(),
|
|
this.below(),
|
|
this.r().below(),
|
|
this.dl().below(),
|
|
this.d().below(),
|
|
this.dr().below())
|
|
}
|
|
}
|
|
|
|
getUnfilteredAdjNeighbors() { return this.getUnfilteredAdjNeighborsIncSelf().filter((pt) => !this.equals(pt)) }
|
|
getUnfilteredDiagNeighbors() { return this.getUnfilteredDiagNeighborsIncSelf().filter((pt) => !this.equals(pt)) }
|
|
getUnfilteredAllNeighbors() { return this.getUnfilteredAllNeighborsIncSelf().filter((pt) => !this.equals(pt)) }
|
|
|
|
add(pt) { return new Point(this.x + pt.x, this.y + pt.y, this.is3D ? this.z + pt.z : undefined) }
|
|
addMut(pt) {
|
|
this.x += pt.x
|
|
this.y += pt.y
|
|
|
|
if (this.is3D) {
|
|
this.z += pt.z
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
sub(pt) { return new Point(this.x - pt.x, this.y - pt.y, this.is3D ? this.z - pt.z : undefined) }
|
|
subMut(pt) {
|
|
this.x -= pt.x
|
|
this.y -= pt.y
|
|
|
|
if (this.is3D) {
|
|
this.z -= pt.z
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
mult(n) { return new Point(this.x * n, this.y * n, this.is3D ? this.z * n : undefined) }
|
|
multMut(n) {
|
|
this.x *= n
|
|
this.y *= n
|
|
|
|
if (this.is3D) {
|
|
this.z *= n
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
neg(n) { return this.mult(-1) }
|
|
negMut(n) { return this.multMut(-1) }
|
|
|
|
squaredMag() { return this.x * this.x + this.y * this.y + (this.is3D ? this.z * this.z : 0) }
|
|
mag() { return Math.sqrt(this.squaredMag()) }
|
|
|
|
squaredDist(pt) { return this.sub(pt).squaredMag() }
|
|
dist(pt) { return this.sub(pt).mag() }
|
|
|
|
readingOrderCompare(pt) {
|
|
if (this.is3D && this.z < pt.z) {
|
|
return -1
|
|
} else if (this.is3D && this.z > pt.z) {
|
|
return 1
|
|
} else if (this.y < pt.y) {
|
|
return -1
|
|
} else if (this.y > pt.y) {
|
|
return 1
|
|
} else if (this.x < pt.x) {
|
|
return -1
|
|
} else if (this.x > pt.x) {
|
|
return 1
|
|
} else {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
copy() { return new Point(this.x, this.y, this.z) }
|
|
toString() { return this.x + "," + this.y + (this.is3D ? "," + this.z : "") }
|
|
}
|
|
|
|
Point.NONE = new Point(null, null)
|
|
|
|
P = function P(...args) {
|
|
return new Point(...args)
|
|
}
|
|
|
|
|
|
Grid = class Grid {
|
|
constructor(w, h, fill = 0) {
|
|
this.width = w
|
|
this.height = h
|
|
this.data = utils.createGridArray(w, h, fill)
|
|
}
|
|
|
|
forEach(func) {
|
|
this.data.map((r, y) => r.map((e, x) => func(e, new Point(x, y), this)))
|
|
return this
|
|
}
|
|
|
|
mapMut(func) { return this.forEach((e, pt, grid) => grid.set(pt, func(e, pt, grid))) }
|
|
|
|
fill(n) { return this.mapMut(() => n) }
|
|
|
|
fillFromArr(arr) {
|
|
if (arr[0].length != this.width) {
|
|
console.warn(`Grid.fillFromArr: Row size ${arr[0].length} does not match grid width ${this.width}`)
|
|
}
|
|
|
|
if (arr.length != this.height) {
|
|
console.warn(`Grid.fillFromArr: Column size ${arr.length} does not match grid height ${this.height}`)
|
|
}
|
|
|
|
return this.mapMut((_, pt) => arr[pt.y][pt.x])
|
|
}
|
|
|
|
fillFromStr(str, sep = "") { return this.fillFromArr(str.split("\n").map((line) => line.split(sep))) }
|
|
|
|
static fromArr(arr) { return new Grid(arr[0].length, arr.length).fillFromArr(arr) }
|
|
static fromStr(str, sep = "") { return Grid.fromArr(str.split("\n").map((line) => line.split(sep))) }
|
|
|
|
get(pt) {
|
|
if (this.contains(pt)) {
|
|
return this.data[pt.y][pt.x]
|
|
} else {
|
|
console.error("Grid.get: Grid does not contain point " + pt + ":\n" + this)
|
|
}
|
|
}
|
|
|
|
set(pt, val) {
|
|
if (this.contains(pt)) {
|
|
this.data[pt.y][pt.x] = val
|
|
} else {
|
|
console.error("Grid.set: does not contain point " + pt + ":\n" + this)
|
|
}
|
|
}
|
|
|
|
getColumn(x) {
|
|
if (x >= 0 && x < this.width) {
|
|
return this.data.map((row) => row[x])
|
|
} else {
|
|
console.error("Grid.getColumn: does not contain column " + x + ":\n" + this)
|
|
}
|
|
}
|
|
|
|
getRow(y) {
|
|
if (y >= 0 && y < this.height) {
|
|
return this.data[y]
|
|
} else {
|
|
console.error("Grid.getRow: does not contain row " + y + ":\n" + this)
|
|
}
|
|
}
|
|
|
|
getSection(pt1, pt2) {
|
|
if (pt2.x >= pt1.x && pt2.y >= pt2.y) {
|
|
return new Grid(pt2.x - pt1.x + 1, pt2.y - pt1.y + 1).mapMut((_, pt) => this.get(pt.add(pt1)))
|
|
} else {
|
|
console.error("Grid.getSection: Second point " + pt2 + " behind first point " + pt1 + ":\n" + this)
|
|
}
|
|
}
|
|
|
|
getRows() {
|
|
return this.data.copy()
|
|
}
|
|
|
|
getColumns() {
|
|
return this.data.transpose()
|
|
}
|
|
|
|
findIndex(func) {
|
|
func = typeof func == "function" ? func : (e) => e == func
|
|
|
|
for (let y = 0; y < this.height; y++) {
|
|
for (let x = 0; x < this.width; x++) {
|
|
let pt = new Point(x, y)
|
|
if (func(this.get(pt), pt, this)) {
|
|
return pt
|
|
}
|
|
}
|
|
}
|
|
|
|
return Point.NONE
|
|
}
|
|
|
|
find(func) {
|
|
return this.get(this.findIndex(func))
|
|
}
|
|
|
|
findAllIndices(func) {
|
|
func = typeof func == "function" ? func : (e) => e == func
|
|
|
|
let points = new PointArray()
|
|
this.forEach((e, pt, grid) => func(e, pt, grid) ? points.push(pt) : 0)
|
|
|
|
return points
|
|
}
|
|
|
|
findAll(func) {
|
|
return this.findAllIndices(func).mapArr((pt) => this.get(pt))
|
|
}
|
|
|
|
count(func) {
|
|
return this.findAllIndices(func).length
|
|
}
|
|
|
|
indexOf(val) {
|
|
return this.findIndex((e) => e == val)
|
|
}
|
|
|
|
contains(pt) { return !pt.is3D && pt.x >= 0 && pt.x < this.width && pt.y >= 0 && pt.y < this.height }
|
|
|
|
getAdjNeighbors(pt) { return pt.getUnfilteredAdjNeighbors().filter((pt) => this.contains(pt)) }
|
|
getAdjNeighborsIncSelf(pt) { return pt.getUnfilteredAdjNeighborsIncSelf().filter((pt) => this.contains(pt)) }
|
|
getDiagNeighbors(pt) { return pt.getUnfilteredDiagNeighbors().filter((pt) => this.contains(pt)) }
|
|
getDiagNeighborsIncSelf(pt) { return pt.getUnfilteredDiagNeighborsIncSelf().filter((pt) => this.contains(pt)) }
|
|
getAllNeighbors(pt) { return pt.getUnfilteredAllNeighbors().filter((pt) => this.contains(pt)) }
|
|
getAllNeighborsIncSelf(pt) { return pt.getUnfilteredAllNeighborsIncSelf().filter((pt) => this.contains(pt)) }
|
|
|
|
getAdjNeighborsThat(pt, func) { return pt.getUnfilteredAdjNeighbors().filter((pt) => this.contains(pt) && func(pt)) }
|
|
getAdjNeighborsIncSelfThat(pt, func) { return pt.getUnfilteredAdjNeighborsIncSelf().filter((pt) => this.contains(pt) && func(pt)) }
|
|
getDiagNeighborsThat(pt, func) { return pt.getUnfilteredDiagNeighbors().filter((pt) => this.contains(pt) && func(pt)) }
|
|
getDiagNeighborsIncSelfThat(pt, func) { return pt.getUnfilteredDiagNeighborsIncSelf().filter((pt) => this.contains(pt) && func(pt)) }
|
|
getAllNeighborsThat(pt, func) { return pt.getUnfilteredAllNeighbors().filter((pt) => this.contains(pt) && func(pt)) }
|
|
getAllNeighborsIncSelfThat(pt, func) { return pt.getUnfilteredAllNeighborsIncSelf().filter((pt) => this.contains(pt) && func(pt)) }
|
|
|
|
static BFS_CONTINUE = 0
|
|
static BFS_STOP = 1
|
|
static BFS_END = 2
|
|
|
|
bfs(pt, func, neighbors = "getAdjNeighborsThat", limit = 1000) {
|
|
let visited = new PointArray()
|
|
let toVisit = new PointArray(pt)
|
|
let count = 0
|
|
let end
|
|
|
|
toVisit[0].path = new PointArray(pt)
|
|
|
|
out: while (toVisit.length > 0 && count++ < limit) {
|
|
let newToVisit = new PointArray()
|
|
|
|
toVisit.sort()
|
|
|
|
for (let i = 0; i < toVisit.length; i++) {
|
|
let v = toVisit[i]
|
|
|
|
v.result = func(this.get(v), v, this, visited)
|
|
|
|
if (v.result == Grid.BFS_CONTINUE) {
|
|
newToVisit.pushUniq(...this[neighbors](v, (pt) => !pt.isIn(visited)).map((pt) => (pt.path = [...v.path, pt], pt)))
|
|
}
|
|
|
|
if (v.result == Grid.BFS_END) {
|
|
end = v
|
|
break out
|
|
}
|
|
}
|
|
|
|
visited.pushUniq(...toVisit)
|
|
toVisit = newToVisit
|
|
}
|
|
|
|
if (count >= limit) {
|
|
console.warn("Limit reached. Aborted.")
|
|
}
|
|
|
|
return end || visited
|
|
}
|
|
|
|
transpose() {
|
|
this.data = this.data.transpose()
|
|
this.width = this.data[0].length
|
|
this.height = this.data.length
|
|
return this
|
|
}
|
|
|
|
reflectX() {
|
|
this.data = this.data.map((row) => row.reverse())
|
|
return this
|
|
}
|
|
|
|
reflectY() {
|
|
this.data.reverse()
|
|
return this
|
|
}
|
|
|
|
rotate90() { return this.transpose().reflectX() }
|
|
rotate180() { return this.reflectX().reflectY() }
|
|
rotate270() { return this.reflectX().transpose() }
|
|
|
|
rotate(n) {
|
|
for (let i = 0; i < Math.abs(n); i++) {
|
|
this[n > 0 ? "rotate90" : "rotate270"]()
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
allTransformations() {
|
|
return [
|
|
this.copy(),
|
|
this.copy().rotate90(),
|
|
this.copy().rotate180(),
|
|
this.copy().rotate270(),
|
|
this.copy().reflectX(),
|
|
this.copy().reflectX().transpose().reflectX(),
|
|
this.copy().reflectY(),
|
|
this.copy().transpose()
|
|
]
|
|
}
|
|
|
|
graphify(neighbors = "getAdjNeighbors", cxn = (node, cxnNode) => node.addCxn(cxnNode, cxnNode.val)) {
|
|
this.mapMut((e) => new Node(e))
|
|
this.forEach((e, pt) => this[neighbors](pt).forEach((pt) => cxn(e, this.get(pt))))
|
|
return
|
|
}
|
|
|
|
copy() { return new Grid(this.width, this.height).mapMut((_, pt) => this.get(pt).copyDeep()) }
|
|
toString(sep = "\t", ...pts) { return this.data.map((r, y) => r.map((e, x) => new Point(x, y).isIn(pts) ? "P" : e).join(sep)).join("\n") }
|
|
print(sep = "\t", ...pts) { console.log(this.toString(sep, pts)) }
|
|
}
|
|
|
|
BinHeap = class BinHeap {
|
|
constructor(cond = (p, c) => p < c) {
|
|
this.cond = cond
|
|
this.data = []
|
|
}
|
|
|
|
getTop() { return this.data[0] }
|
|
getParent(idx) { return idx / 2 | 0 }
|
|
getChildLeft(idx) { return 2 * idx }
|
|
getChildRight(idx) { return 2 * idx + 1 }
|
|
|
|
insert(val) {
|
|
this.up(this.data.push(val) - 1)
|
|
}
|
|
|
|
extract() {
|
|
let res = this.data[0]
|
|
|
|
if (this.data.length > 1) {
|
|
this.data[0] = this.data.pop()
|
|
this.down(0)
|
|
} else {
|
|
this.data = []
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
up(idx) {
|
|
while (this.getParent(idx) != idx && !this.cond(this.data[this.getParent(idx)], this.data[idx])) {
|
|
let tmp = this.data[this.getParent(idx)]
|
|
this.data[this.getParent(idx)] = this.data[idx]
|
|
this.data[idx] = tmp
|
|
idx = this.getParent(idx)
|
|
}
|
|
}
|
|
|
|
down(idx) {
|
|
let largest = idx
|
|
let left = this.getChildLeft(idx)
|
|
let right = this.getChildRight(idx)
|
|
|
|
if (left < this.data.length && this.cond(this.data[left], this.data[largest])) {
|
|
largest = left
|
|
}
|
|
|
|
if (right < this.data.length && this.cond(this.data[right], this.data[largest])) {
|
|
largest = right
|
|
}
|
|
|
|
if (largest != idx) {
|
|
let tmp = this.data[largest]
|
|
this.data[largest] = this.data[idx]
|
|
this.data[idx] = tmp
|
|
|
|
this.down(largest)
|
|
}
|
|
}
|
|
}
|
|
|
|
Cxn = class Cxn {
|
|
constructor(dest, weight = 1) {
|
|
this.dest = dest
|
|
this.weight = weight
|
|
}
|
|
}
|
|
|
|
SearchData = class SearchData {
|
|
constructor(id, dist = Infinity, last = undefined, custom = {}) {
|
|
this.id = id
|
|
this.dist = dist
|
|
this.last = last
|
|
this.custom = custom
|
|
}
|
|
|
|
get(id, dist = Infinity, last = undefined, custom = {}) {
|
|
if (this.id != id) {
|
|
this.id = id
|
|
this.dist = dist
|
|
this.last = last
|
|
this.custom = custom
|
|
}
|
|
|
|
return { dist: this.dist, last: this.last, custom: this.custom }
|
|
}
|
|
|
|
update(id, dist = Infinity, last = undefined, custom = {}) {
|
|
if (this.id != id || this.dist > dist) {
|
|
this.id = id
|
|
this.dist = dist
|
|
this.last = last
|
|
this.custom = custom
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
Node = class Node {
|
|
static GLOBAL_ID = 0
|
|
|
|
constructor(val) {
|
|
this.id = Node.GLOBAL_ID++
|
|
this.val = val
|
|
this.cxns = []
|
|
this.searchData = new SearchData(0)
|
|
}
|
|
|
|
addCxn(node, weight = 1) { this.cxns.push(new Cxn(node, weight)) }
|
|
mapCxnsMut(func) { this.cxns = this.cxns.map(func) }
|
|
filterCxnsMut(func) { this.cxns = this.cxns.filter(func) }
|
|
getWeightTo(node) { return this.cxns.find((cxn) => cxn.dest == node)?.weight }
|
|
|
|
unwrap() {
|
|
let path = [this]
|
|
|
|
while (path[0].searchData.last) {
|
|
path.unshift(path[0].searchData.last)
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
dijkstraTo(dests) {
|
|
if (!Array.isArray(dests)) {
|
|
dests = [dests]
|
|
}
|
|
|
|
let id = Symbol()
|
|
|
|
this.searchData.update(id, 0)
|
|
|
|
console.time("heap gen")
|
|
|
|
let heap = new BinHeap((p, c) => p.searchData.get(id, Infinity, undefined, true).dist < c.searchData.get(id, Infinity, undefined, true).dist)
|
|
let visited = {}
|
|
let toVisit = [this]
|
|
|
|
let i = 0
|
|
|
|
while (toVisit.length) {
|
|
let toVisitNew = []
|
|
|
|
toVisit.forEach((node) => {
|
|
heap.insert(node)
|
|
visited[node.id] = true
|
|
node.cxns.map((cxn) => cxn.dest).map((node) => {
|
|
if (!visited[node.id]) {
|
|
toVisitNew.push(node)
|
|
visited[node.id] = true
|
|
}
|
|
})
|
|
})
|
|
|
|
toVisit = toVisitNew
|
|
|
|
if (++i % 100 == 0) {
|
|
console.log(toVisit.length)
|
|
}
|
|
}
|
|
|
|
console.timeEnd("heap gen")
|
|
console.time("search")
|
|
|
|
i = 0
|
|
|
|
while (heap.data.length) {
|
|
let min = heap.extract()
|
|
let minDist = min.searchData.get(id, Infinity, undefined, true).dist
|
|
min.searchData.custom = false
|
|
|
|
if (dests.includes(min)) {
|
|
console.timeEnd("search")
|
|
min.searchData.get(id)
|
|
return min
|
|
}
|
|
|
|
min.cxns.filter((cxn) => cxn.dest.searchData.get(id).custom).forEach((cxn) => {
|
|
if (cxn.dest.searchData.update(id, minDist + cxn.weight, min, true)) {
|
|
let idx = heap.data.indexOf(cxn.dest)
|
|
if (idx > -1) {
|
|
heap.up(idx)
|
|
}
|
|
}
|
|
})
|
|
|
|
if (++i % 1000 == 0) {
|
|
console.log(heap.data.length)
|
|
}
|
|
}
|
|
|
|
console.timeEnd("search")
|
|
console.warn("Node.dijkstraTo: Could not find a path")
|
|
}
|
|
}
|
|
|
|
PtArray = PointArray = class PointArray extends Array {
|
|
static convert(arr) {
|
|
if (!(arr instanceof PointArray)) {
|
|
arr.__proto__ = PointArray.prototype
|
|
}
|
|
|
|
return arr
|
|
}
|
|
|
|
static revert(arr) {
|
|
if (arr.__proto__ != Array.prototype) {
|
|
arr.__proto__ = Array.prototype
|
|
}
|
|
|
|
return arr
|
|
}
|
|
}
|
|
|
|
let warned = false
|
|
|
|
load = function load() {
|
|
Object.defineProperties(Object.prototype, {
|
|
copyDeep: {
|
|
value: function() {
|
|
return JSON.parse(JSON.stringify(this))
|
|
},
|
|
configurable: true
|
|
},
|
|
num: {
|
|
value: function() {
|
|
if (this.map) {
|
|
return this.map((e) => +e)
|
|
} else if (this.mapMut) {
|
|
return this.mapMut((e) => +e)
|
|
} else {
|
|
console.error("Object.prototype.num: No suitable map method found")
|
|
}
|
|
},
|
|
configurable: true
|
|
}
|
|
})
|
|
|
|
Object.defineProperties(Array.prototype, {
|
|
pt: {
|
|
get: function() {
|
|
return PointArray.convert(this)
|
|
},
|
|
configurable: true
|
|
},
|
|
last: {
|
|
get: function() {
|
|
return this[this.length - 1]
|
|
},
|
|
set: function(val) {
|
|
this[this.length - 1] = val
|
|
},
|
|
configurable: true
|
|
},
|
|
dll: {
|
|
value: function() {
|
|
return new DLL(this)
|
|
},
|
|
configurable: true
|
|
},
|
|
truthy: {
|
|
value: function() {
|
|
return this.filter((e) => e)
|
|
},
|
|
configurable: true
|
|
},
|
|
splitOnElement: {
|
|
value: function(sep) {
|
|
let arr = [[]]
|
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
if (this[i] == sep) {
|
|
arr.push([])
|
|
} else {
|
|
arr[arr.length - 1].push(this[i])
|
|
}
|
|
}
|
|
|
|
return arr
|
|
},
|
|
configurable: true
|
|
},
|
|
copy: {
|
|
value: function() {
|
|
return this.slice()
|
|
},
|
|
configurable: true
|
|
},
|
|
copyDeep: {
|
|
value: function() {
|
|
return JSON.parse(JSON.stringify(this))
|
|
},
|
|
configurable: true
|
|
},
|
|
mapArr: {
|
|
value: function(fn) {
|
|
const mapped = new Array(this.length)
|
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
mapped[i] = fn(this[i], i, this)
|
|
}
|
|
|
|
return mapped
|
|
},
|
|
configurable: true
|
|
},
|
|
sum: {
|
|
value: function(val = 0) {
|
|
return this.reduce((a, b) => a + b, val)
|
|
},
|
|
configurable: true
|
|
},
|
|
prod: {
|
|
value: function(val = 1) {
|
|
return this.reduce((a, b) => a * b, val)
|
|
},
|
|
configurable: true
|
|
},
|
|
cartProduct: {
|
|
value: function(that) {
|
|
return this.flatMap((e) => that.map((f) => [e, f]))
|
|
},
|
|
configurable: true
|
|
},
|
|
flatDeep: {
|
|
value: function() {
|
|
return this.flat(Infinity)
|
|
},
|
|
configurable: true
|
|
},
|
|
transpose: {
|
|
value: function() {
|
|
return this[0].map((_, i) => this.map(e => e[i]))
|
|
},
|
|
configurable: true
|
|
},
|
|
findLast: {
|
|
value: function(func) {
|
|
for (let i = this.length - 1; i >= 0; i--) {
|
|
if (func(this[i], i, this)) {
|
|
return this[i]
|
|
}
|
|
}
|
|
},
|
|
configurable: true
|
|
},
|
|
findLastIndex: {
|
|
value: function(func) {
|
|
for (let i = this.length - 1; i >= 0; i--) {
|
|
if (func(this[i], i, this)) {
|
|
return i
|
|
}
|
|
}
|
|
|
|
return -1
|
|
},
|
|
configurable: true
|
|
},
|
|
interleave: {
|
|
value: function(that) {
|
|
return [this, that].transpose().flat()
|
|
},
|
|
configurable: true
|
|
},
|
|
rotateLeft: {
|
|
value: function(n) {
|
|
let k = (this.length + n) % this.length
|
|
return [...this.slice(k), ...this.slice(0, k)]
|
|
},
|
|
configurable: true
|
|
},
|
|
sub: {
|
|
value: function(that) {
|
|
return this.filter(e => !that.includes(e))
|
|
},
|
|
configurable: true
|
|
},
|
|
int: {
|
|
value: function(that) {
|
|
return this.filter(e => that.includes(e))
|
|
},
|
|
configurable: true
|
|
},
|
|
uniq: {
|
|
value: function() {
|
|
return this.filter((e, i) => this.indexOf(e) == i)
|
|
},
|
|
configurable: true
|
|
},
|
|
pushUniq: {
|
|
value: function(...vals) {
|
|
if (!warned) {
|
|
console.warn("You should probably use a Set")
|
|
warned = true
|
|
}
|
|
|
|
return this.push(...vals.uniq().sub(this))
|
|
},
|
|
configurable: true
|
|
},
|
|
count: {
|
|
value: function(fn) {
|
|
return this.filter(typeof fn == "function" ? fn : (e) => e == fn).length
|
|
},
|
|
configurable: true
|
|
},
|
|
minIndex: {
|
|
value: function(fn = (e) => e, tiebreak) {
|
|
let minval = Infinity
|
|
let min
|
|
let idx
|
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
let val = fn(this[i])
|
|
|
|
if (minval > val ||
|
|
(minval == val && tiebreak && tiebreak(min, this[i], idx, i) > 0)) {
|
|
minval = val
|
|
min = this[i]
|
|
idx = i
|
|
}
|
|
}
|
|
|
|
return idx
|
|
},
|
|
configurable: true
|
|
},
|
|
min: {
|
|
value: function(fn, tiebreak) {
|
|
return this[this.minIndex(fn, tiebreak)]
|
|
},
|
|
configurable: true
|
|
},
|
|
maxIndex: {
|
|
value: function(fn = (e) => e, tiebreak) {
|
|
let maxval = -Infinity
|
|
let max
|
|
let idx
|
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
let val = fn(this[i])
|
|
|
|
if (maxval < val ||
|
|
(maxval == val && tiebreak && tiebreak(max, this[i], idx, i) > 0)) {
|
|
maxval = val
|
|
max = this[i]
|
|
idx = i
|
|
}
|
|
}
|
|
|
|
return idx
|
|
},
|
|
configurable: true
|
|
},
|
|
max: {
|
|
value: function(fn, tiebreak) {
|
|
return this[this.maxIndex(fn, tiebreak)]
|
|
},
|
|
configurable: true
|
|
},
|
|
mean: {
|
|
value: function() {
|
|
return this.sum() / this.length
|
|
},
|
|
configurable: true
|
|
},
|
|
medianNumeric: {
|
|
value: function() {
|
|
let sorted = this.copy().sort((a, b) => a - b)
|
|
|
|
if (sorted.length % 2) {
|
|
return sorted[(sorted.length - 1) / 2]
|
|
} else {
|
|
return (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
|
|
}
|
|
},
|
|
},
|
|
freqs: {
|
|
value: function() {
|
|
return this.uniq().map((e) => [e, this.count(e)])
|
|
},
|
|
configurable: true
|
|
},
|
|
mode: {
|
|
value: function(tiebreak) {
|
|
return this.freqs().max((e) => e[1], (a, b, ai, bi) => tiebreak(a[0], b[0]))[0]
|
|
},
|
|
configurable: true
|
|
},
|
|
antimode: {
|
|
value: function(tiebreak) {
|
|
return this.freqs().min((e) => e[1], (a, b, ai, bi) => tiebreak(a[0], b[0]))[0]
|
|
},
|
|
configurable: true
|
|
}
|
|
})
|
|
|
|
Object.defineProperties(PointArray.prototype, {
|
|
arr: {
|
|
get: function() {
|
|
return PointArray.revert(this)
|
|
},
|
|
configurable: true
|
|
},
|
|
splitOnElement: {
|
|
value: function(sep) {
|
|
let arr = [new PointArray()]
|
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
if (this[i].equals(sep)) {
|
|
arr.push(new PointArray())
|
|
} else {
|
|
arr[arr.length - 1].push(this[i])
|
|
}
|
|
}
|
|
|
|
return arr
|
|
},
|
|
configurable: true
|
|
},
|
|
map: {
|
|
value: function(fn) {
|
|
const mapped = new PointArray(this.length)
|
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
mapped[i] = fn(this[i], i, this)
|
|
}
|
|
|
|
return mapped
|
|
},
|
|
configurable: true
|
|
},
|
|
cartProduct: {
|
|
value: function(that) {
|
|
return this.flatMap((e) => that.map((f) => new PointArray(e, f)))
|
|
},
|
|
configurable: true
|
|
},
|
|
interleave: {
|
|
value: function(that = new PointArray()) {
|
|
return new PointArray(this, that).transpose().flat()
|
|
},
|
|
configurable: true
|
|
},
|
|
rotateLeft: {
|
|
value: function(n) {
|
|
if (this.length == 1) {
|
|
return this.copy()
|
|
}
|
|
|
|
let k = (this.length + n) % this.length
|
|
return new PointArray(...this.slice(k), ...this.slice(0, k))
|
|
},
|
|
configurable: true
|
|
},
|
|
sort: {
|
|
value: function(func = (a, b) => a.readingOrderCompare(b)) {
|
|
return Array.prototype.sort.apply(this, [func])
|
|
},
|
|
configurable: true
|
|
},
|
|
includes: {
|
|
value: function(pt) {
|
|
return pt.isIn(this)
|
|
},
|
|
configurable: true
|
|
},
|
|
indexOf: {
|
|
value: function(pt) {
|
|
return pt.indexIn(this)
|
|
},
|
|
configurable: true
|
|
},
|
|
lastIndexOf: {
|
|
value: function(pt) {
|
|
return pt.lastIndexIn(this)
|
|
},
|
|
configurable: true
|
|
},
|
|
sub: {
|
|
value: function(that) {
|
|
return this.filter(e => !that.pt.includes(e))
|
|
},
|
|
configurable: true
|
|
},
|
|
int: {
|
|
value: function(that) {
|
|
return this.filter(e => that.pt.includes(e))
|
|
},
|
|
configurable: true
|
|
},
|
|
uniq: {
|
|
value: function() {
|
|
return this.filter((e, i) => this.pt.indexOf(e) == i)
|
|
},
|
|
configurable: true
|
|
},
|
|
pushUniq: {
|
|
value: function(...vals) {
|
|
return this.push(...vals.pt.uniq().pt.sub(this))
|
|
},
|
|
configurable: true
|
|
},
|
|
count: {
|
|
value: function(fn) {
|
|
return this.filter(typeof fn == "function" ? fn : (e) => e.equals(fn)).length
|
|
},
|
|
configurable: true
|
|
}
|
|
})
|
|
}
|
|
|
|
load()
|
|
utils = {
|
|
log: (e, copy = false) => (console.log(copy ? e.copyDeep() : e), e),
|
|
fetch: (url) => fetch(url).then(e => e.text()),
|
|
fetchEval: (url) => utils.fetch(url).then(e => eval(e)),
|
|
signAgnosticInclusiveRange: (a, b, s = Math.sign(a - b)) => Array((a - b) * s + 1).fill().map((_, i) => a - i * s),
|
|
createGridArray: (w, h, fill = undefined) => Array(h).fill().map(() => Array(w).fill(fill))
|
|
}
|
|
|
|
if (typeof window == "undefined" && process.argv[2] == "test") {
|
|
const fs = require("fs")
|
|
|
|
function test(name, answer, func, ...args) {
|
|
console.time(name)
|
|
let res = func(...args)
|
|
console.timeEnd(name)
|
|
|
|
console.log(`${name}: Got ${res}, expected ${answer}`)
|
|
|
|
if (res == answer) {
|
|
console.log(`${name}: SUCCESS`)
|
|
} else {
|
|
console.error(`${name}: FAIL`)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
const year = "2021"
|
|
|
|
for (let i = +process.argv[3] || 1; i <= 25; i++) {
|
|
const func = require(`./${year}/${i}.js`)
|
|
const input = fs.readFileSync(`./${year}/inputs/${i}`, "utf8")
|
|
const answers = fs.readFileSync(`./${year}/answers/${i}`, "utf8").split("\n-----\n")
|
|
|
|
if (i != 25) {
|
|
test(`${year} day ${i} part 1`, answers[0], func, input, false)
|
|
test(`${year} day ${i} part 2`, answers[1], func, input, true)
|
|
} else {
|
|
test(`${year} day ${i}`, answers[0], func, input)
|
|
}
|
|
}
|
|
}
|
|
if (typeof window == "undefined") {
|
|
debugger
|
|
}
|