From a37eee947f3b1a0b8e1a37e8c40711310c308824 Mon Sep 17 00:00:00 2001 From: nim-ka Date: Wed, 29 Dec 2021 03:15:45 -0500 Subject: [PATCH] init --- binheap.js | 60 +++++ cat.sh | 3 + dll.js | 172 ++++++++++++ graph.js | 132 +++++++++ grid.js | 178 +++++++++++++ out.js | 763 +++++++++++++++++++++++++++++++++++++++++++++++++++++ proto.js | 153 +++++++++++ pt.js | 61 +++++ utils.js | 7 + 9 files changed, 1529 insertions(+) create mode 100644 binheap.js create mode 100644 cat.sh create mode 100644 dll.js create mode 100644 graph.js create mode 100644 grid.js create mode 100644 out.js create mode 100644 proto.js create mode 100644 pt.js create mode 100644 utils.js diff --git a/binheap.js b/binheap.js new file mode 100644 index 0000000..c0cf34f --- /dev/null +++ b/binheap.js @@ -0,0 +1,60 @@ +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) + } + } +} + diff --git a/cat.sh b/cat.sh new file mode 100644 index 0000000..65115c3 --- /dev/null +++ b/cat.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cat dll.js pt.js grid.js binheap.js graph.js proto.js utils.js > out.js diff --git a/dll.js b/dll.js new file mode 100644 index 0000000..422270b --- /dev/null +++ b/dll.js @@ -0,0 +1,172 @@ +// TODO: +// - copy method +// - remove range method + +class DLLNode { + constructor(val, skip = this, prev = this, next = this) { + this.val = val + this.skip = skip + this.prev = prev + this.next = next + } + + adv(n) { + 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 + } +} + +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() } +} + diff --git a/graph.js b/graph.js new file mode 100644 index 0000000..d39a6b8 --- /dev/null +++ b/graph.js @@ -0,0 +1,132 @@ +class Cxn { + constructor(dest, weight = 1) { + this.dest = dest + this.weight = weight + } +} + +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 + } +} + +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 + } + + dijkstra(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] + + 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 + console.log(toVisit.length) + } + + console.timeEnd("heap gen") + console.time("search") + + let 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.dijkstra: Could not find a path") + } +} + diff --git a/grid.js b/grid.js new file mode 100644 index 0000000..dd99d68 --- /dev/null +++ b/grid.js @@ -0,0 +1,178 @@ +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) { return this.fillFromArr(str.split("\n")) } + + static fromStr(str) { + let arr = str.split("\n") + return new Grid(arr[0].length, arr.length).fillFromArr(arr) + } + + 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) + } + } + + findAll(func) { + let points = [] + this.forEach((e, pt, grid) => func(e, pt, grid) ? points.push(pt) : 0) + return points + } + + contains(pt) { return pt.x >= 0 && pt.x < this.width && pt.y >= 0 && pt.y < this.height } + + getAdjNeighbors(pt) { return pt.getUnfilteredAdjNeighbors().filter((pt) => this.contains(pt)) } + getDiagNeighbors(pt) { return pt.getUnfilteredDiagNeighbors().filter((pt) => this.contains(pt)) } + getAllNeighbors(pt) { return pt.getUnfilteredAllNeighbors().filter((pt) => this.contains(pt)) } + getAdjNeighborsThat(pt, func) { return pt.getUnfilteredAdjNeighbors().filter((pt) => this.contains(pt) && func(pt)) } + getDiagNeighborsThat(pt, func) { return pt.getUnfilteredDiagNeighbors().filter((pt) => this.contains(pt) && func(pt)) } + getAllNeighborsThat(pt, func) { return pt.getUnfilteredAllNeighbors().filter((pt) => this.contains(pt) && func(pt)) } + + static BFS_CONTINUE = 0 + static BFS_STOP = 1 + static BFS_END = 2 + + bfs(pt, func, limit = 1000) { + let visited = [].pt + let toVisit = [pt].pt + let count = 0 + let end + + toVisit[0].path = [pt] + + out: while (toVisit.length > 0 && count++ < limit) { + let newToVisit = [].pt + + toVisit.sort() + + for (let i = 0; i < toVisit.length; i++) { + let result = func(this.get(toVisit[i]), toVisit[i], this, visited); + + if (result == Grid.BFS_CONTINUE) { + newToVisit.pushUniq(...this.getAdjNeighborsThat(toVisit[i], (pt) => !pt.isIn(visited)).map((pt) => (pt.path = [...toVisit[i].path, pt], pt))) + } + + if (result == Grid.BFS_END) { + end = toVisit[i] + break out + } + } + + visited.pushUniq(...toVisit) + toVisit = newToVisit + } + + if (count >= limit) { + console.warn("Limit reached. Aborted.") + } + + return end || visited[visited.length - 1] + } + + 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() + ] + } + + 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)) } +} + diff --git a/out.js b/out.js new file mode 100644 index 0000000..5f4cef8 --- /dev/null +++ b/out.js @@ -0,0 +1,763 @@ +// TODO: +// - copy method +// - remove range method + +class DLLNode { + constructor(val, skip = this, prev = this, next = this) { + this.val = val + this.skip = skip + this.prev = prev + this.next = next + } + + adv(n) { + 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 + } +} + +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() } +} + +class Point { + constructor(x, y) { + this.x = x + this.y = y + } + + equals(pt) { return this.x == pt.x && this.y == pt.y } + 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) } + + 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() } + + getUnfilteredAdjNeighbors() { return [this.u(), this.l(), this.r(), this.d()] } + getUnfilteredDiagNeighbors() { return [this.ul(), this.ur(), this.dl(), this.dr()] } + getUnfilteredAllNeighbors() { return [...this.getUnfilteredAdjNeighbors(), ...this.getUnfilteredDiagNeighbors()] } + + add(pt) { return new Point(this.x + pt.x, this.y + pt.y) } + addMut(pt) { + this.x += pt.x + this.y += pt.y + return this + } + + sub(pt) { return new Point(this.x - pt.x, this.y - pt.y) } + subMut(pt) { + this.x -= pt.x + this.y -= pt.y + return this + } + + squaredMag() { return this.x * this.x + this.y * this.y } + mag() { return Math.sqrt(this.squaredMag()) } + + squaredDist(pt) { return this.sub(pt).squaredMag() } + dist(pt) { return this.sub(pt).mag() } + + readingOrderCompare(pt) { return this.y < pt.y ? -1 : this.y > pt.y ? 1 : this.x < pt.x ? -1 : this.x > pt.x ? 1 : 0 } + + copy() { return new Point(this.x, this.y) } + toString() { return this.x + "," + this.y } +} + +Pt = Point + +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) { return this.fillFromArr(str.split("\n")) } + + static fromStr(str) { + let arr = str.split("\n") + return new Grid(arr[0].length, arr.length).fillFromArr(arr) + } + + 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) + } + } + + findAll(func) { + let points = [] + this.forEach((e, pt, grid) => func(e, pt, grid) ? points.push(pt) : 0) + return points + } + + contains(pt) { return pt.x >= 0 && pt.x < this.width && pt.y >= 0 && pt.y < this.height } + + getAdjNeighbors(pt) { return pt.getUnfilteredAdjNeighbors().filter((pt) => this.contains(pt)) } + getDiagNeighbors(pt) { return pt.getUnfilteredDiagNeighbors().filter((pt) => this.contains(pt)) } + getAllNeighbors(pt) { return pt.getUnfilteredAllNeighbors().filter((pt) => this.contains(pt)) } + getAdjNeighborsThat(pt, func) { return pt.getUnfilteredAdjNeighbors().filter((pt) => this.contains(pt) && func(pt)) } + getDiagNeighborsThat(pt, func) { return pt.getUnfilteredDiagNeighbors().filter((pt) => this.contains(pt) && func(pt)) } + getAllNeighborsThat(pt, func) { return pt.getUnfilteredAllNeighbors().filter((pt) => this.contains(pt) && func(pt)) } + + static BFS_CONTINUE = 0 + static BFS_STOP = 1 + static BFS_END = 2 + + bfs(pt, func, limit = 1000) { + let visited = [].pt + let toVisit = [pt].pt + let count = 0 + let end + + toVisit[0].path = [pt] + + out: while (toVisit.length > 0 && count++ < limit) { + let newToVisit = [].pt + + toVisit.sort() + + for (let i = 0; i < toVisit.length; i++) { + let result = func(this.get(toVisit[i]), toVisit[i], this, visited); + + if (result == Grid.BFS_CONTINUE) { + newToVisit.pushUniq(...this.getAdjNeighborsThat(toVisit[i], (pt) => !pt.isIn(visited)).map((pt) => (pt.path = [...toVisit[i].path, pt], pt))) + } + + if (result == Grid.BFS_END) { + end = toVisit[i] + break out + } + } + + visited.pushUniq(...toVisit) + toVisit = newToVisit + } + + if (count >= limit) { + console.warn("Limit reached. Aborted.") + } + + return end || visited[visited.length - 1] + } + + 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() + ] + } + + 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)) } +} + +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) + } + } +} + +class Cxn { + constructor(dest, weight = 1) { + this.dest = dest + this.weight = weight + } +} + +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 + } +} + +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 + } + + dijkstra(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] + + 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 + console.log(toVisit.length) + } + + console.timeEnd("heap gen") + console.time("search") + + let 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.dijkstra: Could not find a path") + } +} + +Object.defineProperty(Object.prototype, "copyDeep", { + value: function() { + return JSON.parse(JSON.stringify(this)) + } +}) + +Object.defineProperties(Array.prototype, { + dll: { + value: function() { + return new DLL(this) + } + }, + copy: { + value: function() { + return this.slice() + } + }, + copyDeep: { + value: function() { + return JSON.parse(JSON.stringify(this)) + } + }, + sum: { + value: function() { + return this.reduce((a, b) => a + b) + } + }, + flatDeep: { + value: function() { + return this.flat(Infinity) + } + }, + transpose: { + value: function() { + return Array(this[0].length).fill().map((_, i) => this.map(e => e[i])) + } + }, + findLast: { + value: function(func) { + for (let i = this.length - 1; i >= 0; i--) { + if (func(this[i], i, this)) { + return this[i] + } + } + } + }, + findLastIndex: { + value: function(func) { + for (let i = this.length - 1; i >= 0; i--) { + if (func(this[i], i, this)) { + return i + } + } + + return -1 + } + }, + interleave: { + value: function(that) { + return [this, that].transpose().flat() + } + }, + rotate: { + value: function(n) { + let k = (this.length + n) % this.length + return [...this.slice(k), ...this.slice(0, k)] + } + }, + sub: { + value: function(that) { + return this.filter(e => !that.includes(e)) + } + }, + int: { + value: function(that) { + return this.filter(e => that.includes(e)) + } + }, + uniq: { + value: function() { + return this.filter((e, i) => this.lastIndexOf(e) == i) + } + }, + pushUniq: { + value: function(...vals) { + if (!this.warned) { + console.warn("You should probably use a Set") + this.warned = true + } + + return this.push(...vals.uniq().sub(this)) + } + } +}) + +class PointArray extends Array { + static convert(arr) { + arr.__proto__ = PointArray.prototype + return arr + } +} + +PtArray = PointArray + +Object.defineProperty(Array.prototype, "pt", { + get: function() { + return PointArray.convert(this) + } +}) + +Object.defineProperties(PointArray.prototype, { + sort: { + value: function(func = (a, b) => a.readingOrderCompare(b)) { + this.sort(func) + } + }, + includes: { + value: function(pt) { + return pt.isIn(this) + } + }, + indexOf: { + value: function(pt) { + return pt.indexIn(this) + } + }, + lastIndexOf: { + value: function(pt) { + return pt.lastIndexIn(this) + } + }, + sub: { + value: function(that) { + return this.filter(e => !that.pt.includes(e)) + } + }, + int: { + value: function(that) { + return this.filter(e => that.pt.includes(e)) + } + }, + uniq: { + value: function() { + return this.filter((e, i) => this.pt.lastIndexOf(e) == i) + } + }, + pushUniq: { + value: function(...vals) { + return this.push(...vals.pt.uniq().pt.sub(this)) + } + } +}) + +utils = { + 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)) +} + diff --git a/proto.js b/proto.js new file mode 100644 index 0000000..0f3310b --- /dev/null +++ b/proto.js @@ -0,0 +1,153 @@ +Object.defineProperty(Object.prototype, "copyDeep", { + value: function() { + return JSON.parse(JSON.stringify(this)) + } +}) + +Object.defineProperties(Array.prototype, { + dll: { + value: function() { + return new DLL(this) + } + }, + copy: { + value: function() { + return this.slice() + } + }, + copyDeep: { + value: function() { + return JSON.parse(JSON.stringify(this)) + } + }, + sum: { + value: function() { + return this.reduce((a, b) => a + b) + } + }, + flatDeep: { + value: function() { + return this.flat(Infinity) + } + }, + transpose: { + value: function() { + return Array(this[0].length).fill().map((_, i) => this.map(e => e[i])) + } + }, + findLast: { + value: function(func) { + for (let i = this.length - 1; i >= 0; i--) { + if (func(this[i], i, this)) { + return this[i] + } + } + } + }, + findLastIndex: { + value: function(func) { + for (let i = this.length - 1; i >= 0; i--) { + if (func(this[i], i, this)) { + return i + } + } + + return -1 + } + }, + interleave: { + value: function(that) { + return [this, that].transpose().flat() + } + }, + rotate: { + value: function(n) { + let k = (this.length + n) % this.length + return [...this.slice(k), ...this.slice(0, k)] + } + }, + sub: { + value: function(that) { + return this.filter(e => !that.includes(e)) + } + }, + int: { + value: function(that) { + return this.filter(e => that.includes(e)) + } + }, + uniq: { + value: function() { + return this.filter((e, i) => this.lastIndexOf(e) == i) + } + }, + pushUniq: { + value: function(...vals) { + if (!this.warned) { + console.warn("You should probably use a Set") + this.warned = true + } + + return this.push(...vals.uniq().sub(this)) + } + } +}) + +class PointArray extends Array { + static convert(arr) { + arr.__proto__ = PointArray.prototype + return arr + } +} + +PtArray = PointArray + +Object.defineProperty(Array.prototype, "pt", { + get: function() { + return PointArray.convert(this) + } +}) + +Object.defineProperties(PointArray.prototype, { + sort: { + value: function(func = (a, b) => a.readingOrderCompare(b)) { + this.sort(func) + } + }, + includes: { + value: function(pt) { + return pt.isIn(this) + } + }, + indexOf: { + value: function(pt) { + return pt.indexIn(this) + } + }, + lastIndexOf: { + value: function(pt) { + return pt.lastIndexIn(this) + } + }, + sub: { + value: function(that) { + return this.filter(e => !that.pt.includes(e)) + } + }, + int: { + value: function(that) { + return this.filter(e => that.pt.includes(e)) + } + }, + uniq: { + value: function() { + return this.filter((e, i) => this.pt.lastIndexOf(e) == i) + } + }, + pushUniq: { + value: function(...vals) { + return this.push(...vals.pt.uniq().pt.sub(this)) + } + } +}) + diff --git a/pt.js b/pt.js new file mode 100644 index 0000000..5652372 --- /dev/null +++ b/pt.js @@ -0,0 +1,61 @@ +class Point { + constructor(x, y) { + this.x = x + this.y = y + } + + equals(pt) { return this.x == pt.x && this.y == pt.y } + 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) } + + 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() } + + getUnfilteredAdjNeighbors() { return [this.u(), this.l(), this.r(), this.d()] } + getUnfilteredDiagNeighbors() { return [this.ul(), this.ur(), this.dl(), this.dr()] } + getUnfilteredAllNeighbors() { return [...this.getUnfilteredAdjNeighbors(), ...this.getUnfilteredDiagNeighbors()] } + + add(pt) { return new Point(this.x + pt.x, this.y + pt.y) } + addMut(pt) { + this.x += pt.x + this.y += pt.y + return this + } + + sub(pt) { return new Point(this.x - pt.x, this.y - pt.y) } + subMut(pt) { + this.x -= pt.x + this.y -= pt.y + return this + } + + squaredMag() { return this.x * this.x + this.y * this.y } + mag() { return Math.sqrt(this.squaredMag()) } + + squaredDist(pt) { return this.sub(pt).squaredMag() } + dist(pt) { return this.sub(pt).mag() } + + readingOrderCompare(pt) { return this.y < pt.y ? -1 : this.y > pt.y ? 1 : this.x < pt.x ? -1 : this.x > pt.x ? 1 : 0 } + + copy() { return new Point(this.x, this.y) } + toString() { return this.x + "," + this.y } +} + +Pt = Point + diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..3a80263 --- /dev/null +++ b/utils.js @@ -0,0 +1,7 @@ +utils = { + 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)) +} +