11 files changed
+266
-79
No files matched your search
+12
-5
@@ -9,6 +9,7 @@ export type Repeated<
|
||||
R extends readonly T[] = [],
|
||||
> = R['length'] extends N ? R : Repeated<T, N, readonly [T, ...R]>
|
||||
|
||||
// deno-lint-ignore ban-types
|
||||
export type Prettify<T> = { [K in keyof T]: T[K] } & {}
|
||||
|
||||
/**
|
||||
@@ -22,7 +23,8 @@ export function* fill<T>(value = 0 as unknown as T, length = Infinity): Generato
|
||||
}
|
||||
|
||||
/**
|
||||
* A ganerator that yields numbers between a range, akin to Python's {@link https://docs.python.org/3.11/library/stdtypes.html#ranges range}.
|
||||
* A ganerator that yields numbers between a range, akin to Python's
|
||||
* {@link https://docs.python.org/3.11/library/stdtypes.html#ranges range}.
|
||||
* If `start` is greater than `end`, the range will decrement accordingly
|
||||
*
|
||||
* @example
|
||||
@@ -43,7 +45,8 @@ export function range(start: number, end: number, step: number): Generator<numbe
|
||||
export function* range(a: number, b?: number, c?: number) {
|
||||
const [start, end, step] = (
|
||||
typeof b === "undefined" ? [0, a, 1] :
|
||||
typeof c === "undefined" ? [a, b, 1] : [a, b, Math.abs(c)]
|
||||
typeof c === "undefined" ? [a, b, 1]
|
||||
: [a, b, Math.abs(c)]
|
||||
)
|
||||
|
||||
const positive = start < end
|
||||
@@ -106,12 +109,16 @@ export function removeFromArraybyIndexes<T>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Divides an array into two by a given predicate function. Those that pass are put into the first, the rest are put in the second.
|
||||
*
|
||||
* Divides an array into two by a given predicate function.
|
||||
* Those that pass are put into the first, the rest are put in the second.
|
||||
*
|
||||
* @author MindfulMinun
|
||||
* @since 2022-06-28
|
||||
*/
|
||||
export function divide<T>(list: T[], predicate: (this: typeof list, value: T, index: number, array: typeof list) => boolean): [T[], T[]] {
|
||||
export function divide<T>(
|
||||
list: T[],
|
||||
predicate: (this: typeof list, value: T, index: number, array: typeof list) => boolean
|
||||
): [T[], T[]] {
|
||||
const pass: T[] = []
|
||||
const fail: T[] = []
|
||||
|
||||
|
||||
Regular → Executable
+51
-12
@@ -1,15 +1,54 @@
|
||||
export interface ShellSpawnerOpts extends Omit<Deno.CommandOptions, 'args'> {
|
||||
shell: string
|
||||
shellArgs: (commandStr: string) => string[]
|
||||
autospawn: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a shell command that can be aborted using an `AbortSignal`
|
||||
* @since 2021-08-13
|
||||
* Shell spawner.
|
||||
* ```
|
||||
* const $: ShellSpawner = koopa()
|
||||
* await $`rm -rf /home/*`
|
||||
* ```
|
||||
*/
|
||||
export async function abortableExec(opts: { cmd: string[], signal?: AbortSignal }): Promise<void> {
|
||||
const { cmd, signal } = opts
|
||||
if (signal && signal.aborted) return
|
||||
const p = Deno.run({
|
||||
cmd,
|
||||
stdout: "null"
|
||||
})
|
||||
signal && signal.addEventListener('abort', () => p.kill("SIGINT"))
|
||||
await p.status()
|
||||
return
|
||||
export type ShellSpawner<R = Deno.Command> = (a: string | TemplateStringsArray, ...values: unknown[]) => R
|
||||
|
||||
/**
|
||||
* Run a shell script.
|
||||
* Factory facade to easily interact and run shell scripts
|
||||
* @since 2023-11-25
|
||||
*/
|
||||
export default function koopa(opts: Partial<ShellSpawnerOpts> = {}) {
|
||||
const actualOpts: ShellSpawnerOpts = {
|
||||
shell: 'sh',
|
||||
shellArgs: cmd => ['-c', cmd],
|
||||
autospawn: true,
|
||||
...opts,
|
||||
}
|
||||
|
||||
const factory: ShellSpawner = function (
|
||||
templ: TemplateStringsArray | string,
|
||||
...values: unknown[]
|
||||
) {
|
||||
// TODO: Implement actual escaping
|
||||
const escape = (a: string) => a
|
||||
let execStr = ''
|
||||
if (typeof templ === 'string') {
|
||||
execStr += templ
|
||||
} else {
|
||||
for (let i = 0; i < templ.length; i++) {
|
||||
execStr += templ[i]
|
||||
execStr += typeof values[i] !== 'undefined'
|
||||
? escape('' + values[i])
|
||||
: ''
|
||||
}
|
||||
}
|
||||
|
||||
return new Deno.Command(actualOpts.shell, {
|
||||
args: actualOpts.shellArgs(execStr),
|
||||
...opts
|
||||
})
|
||||
}
|
||||
|
||||
return factory
|
||||
}
|
||||
+39
-6
@@ -21,7 +21,7 @@ export function dedent(
|
||||
let strings = Array.from(typeof templ === 'string' ? [templ] : templ)
|
||||
|
||||
// 1. Remove trailing whitespace.
|
||||
strings[strings.length - 1] = strings[strings.length - 1].trimRight()
|
||||
strings[strings.length - 1] = strings[strings.length - 1].trimEnd()
|
||||
|
||||
// 2. Find all line breaks to determine the highest common indentation level.
|
||||
const indentLengths = strings.reduce((arr, str) => {
|
||||
@@ -82,15 +82,48 @@ export function templateNoop(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes HTML on the server-side.
|
||||
*
|
||||
* @remarks Avoid calling this method twice on the same string.
|
||||
*
|
||||
* @author MindfulMinun
|
||||
* @since 2022-05-20
|
||||
*/
|
||||
export function html(
|
||||
templ: TemplateStringsArray | string,
|
||||
...values: unknown[]
|
||||
): string {
|
||||
if (typeof templ === 'string') return xss(templ)
|
||||
return templ.reduce((result, currentString, i) =>
|
||||
result + currentString + (typeof values[i] !== 'undefined' ? xss('' + values[i]) : ''),
|
||||
'' // Start with the empty string
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes unsafe strings for use in HTML
|
||||
*
|
||||
* ```js
|
||||
* xss(`<img src="/bogus/path" onerror="alert('xss')">`)
|
||||
* ```
|
||||
* @remarks Avoid calling this method twice on the same string.
|
||||
*
|
||||
* @example
|
||||
* xss(`<img src=":(" onerror="alert('xss')">`)
|
||||
* // -> `<img src=":(" onerror="alert('xss')">`
|
||||
*
|
||||
* const unsafe = `Bobby Tables <img src=":(" onerror="alert('xss')">`
|
||||
* xss`<p>My name is <strong>${unsafe}</strong></p>`
|
||||
* // -> `<p>My name is <strong>Bobby Tables <img src=":(" onerror="alert('xss')"></strong></p>`
|
||||
* @author MindfulMinun
|
||||
* @since 2020-06-23
|
||||
*/
|
||||
export function xss(unsafe: string): string {
|
||||
return unsafe.replace(/[&<>"'\/]/g, key => HTML_ESCAPES[key as keyof typeof HTML_ESCAPES])
|
||||
export function xss(unsafe: string): string
|
||||
export function xss(templ: TemplateStringsArray, ...values: unknown[]): string
|
||||
export function xss(templ: string | TemplateStringsArray, ...values: unknown[]): string {
|
||||
if (typeof templ === 'string') {
|
||||
return templ.replace(/[&<>"'\/]/g, key => HTML_ESCAPES[key as keyof typeof HTML_ESCAPES])
|
||||
}
|
||||
return templ.reduce((result, currentString, i) =>
|
||||
result + currentString + (typeof values[i] !== 'undefined' ? xss('' + values[i]) : ''),
|
||||
'' // Start with the empty string
|
||||
)
|
||||
}
|
||||
@@ -52,6 +52,16 @@ Deno.test('GraphSolver::bidi Path', () => {
|
||||
)
|
||||
})
|
||||
|
||||
Deno.test('GraphSolver::kruskalPresorted', () => {
|
||||
const G = g3()
|
||||
const solver = new GraphSolver(G)
|
||||
const set = solver.kruskalPresorted()
|
||||
// const expected = new Set<string>(['GH', 'FG', 'CI', 'AB', 'CF', 'GI', 'CD', 'AH'])
|
||||
// ;[...set].map(e => e.id).forEach(e => expected.delete(e))
|
||||
// if (expected.size > 0) throw Error(`Missing edges: ${[...expected].join(', ')}`)H
|
||||
throw Error(set.size + '')
|
||||
})
|
||||
|
||||
function assertPathsEqual<vData, eData>(
|
||||
path: Path<vData, eData> | null | undefined,
|
||||
expectedV: string[],
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import {
|
||||
choose, fill, range, limit
|
||||
fill, range, limit
|
||||
} from "../core/iterable.ts"
|
||||
|
||||
import { assertThrows, assertEquals, assert } from "https://deno.land/[email protected]/testing/asserts.ts"
|
||||
|
||||
Deno.test("choose: throw on empty array", () => {
|
||||
assertThrows(() => choose([]))
|
||||
})
|
||||
import { assertEquals, assert } from "https://deno.land/[email protected]/testing/asserts.ts"
|
||||
|
||||
Deno.test("fill, yield the same value every time", () => {
|
||||
const count = 50_000
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { range } from "../core/iterable.ts";
|
||||
import { BinaryHeap, Queue, Stack } from "../tools/structures.ts"
|
||||
import { Queue } from "../tools/structures/Queue.ts"
|
||||
import { Stack } from "../tools/structures/Stack.ts"
|
||||
import { BinaryHeap } from "../tools/structures/Heap.ts"
|
||||
|
||||
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts"
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Implement Dijkstra's, Prim and Kruskal's, Topological sort, maybe like Ford-Fulkerson, maybe Christofides
|
||||
+38
-13
@@ -1,5 +1,8 @@
|
||||
import { Graph, Vertex, VertexType, Edge, EdgeType, Path, PathType } from "./graph.ts"
|
||||
import { Queue, Stack } from "../structures.ts"
|
||||
import { Queue } from "../structures/Queue.ts"
|
||||
import { Stack } from "../structures/Stack.ts"
|
||||
import { BinaryHeap } from "../structures/Heap.ts"
|
||||
import { DisjointSet } from "../structures/DisjointSet.ts"
|
||||
|
||||
/**
|
||||
* Describes a search function for DFS and BFS.
|
||||
@@ -149,6 +152,10 @@ export class GraphSolver<vData, eData> {
|
||||
return null
|
||||
}
|
||||
|
||||
dijkstra() {
|
||||
// TODO: Implement Dijkstra's algorithm
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the edges by their weight ascending, then performs {@link kruskalPresorted | Kruskal's algorithm},
|
||||
* an algorithm for determining a minimum-spanning forest of a graph.
|
||||
@@ -165,31 +172,49 @@ export class GraphSolver<vData, eData> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs Kruskal's algorithm, assuming that the edges have been presorted.
|
||||
* Performs Kruskal's algorithm, ~~assuming that the edges have been presorted.~~
|
||||
* This algorithm returns a set of the edges that span the graph.
|
||||
*
|
||||
* Kruskal's greedy algorithm determines a minimum-spanning forest of a weighted, *undirected* graph.
|
||||
*
|
||||
* This method assumes that the edges have been sorted by their weight ascending, allowing it to run in `O(m)` time.
|
||||
* If the edges have not been sorted, use {@link kruskalWithSort} instead.
|
||||
* ~~This method assumes that the edges have been sorted by their weight ascending, allowing it to run in `O(m)` time.
|
||||
* If the edges have not been sorted, use {@link kruskalWithSort} instead.~~
|
||||
* @author MindfulMinun
|
||||
* @since 2023-03-30
|
||||
*/
|
||||
kruskalPresorted(edges?: Edge<vData, eData>[]) {
|
||||
kruskalPresorted(edges?: Edge<vData, eData>[]): Set<Edge<vData, eData>>
|
||||
kruskalPresorted(edges?: Iterable<Edge<vData, eData>>): Set<Edge<vData, eData>> {
|
||||
const forest = new Set<Edge<vData, eData>>()
|
||||
/** Keeps track of the reachable vertices. Used to ensure that newly added edges do not create a cycle. */
|
||||
const reach = new WeakSet<Vertex<vData, eData>>()
|
||||
const ordered: Iterable<Edge<vData, eData>> = edges ?? this.G.edges.values()
|
||||
const djs = new DisjointSet<string>()
|
||||
|
||||
for (const E of ordered) {
|
||||
if (forest.has(E)) continue
|
||||
if (reach.has(E.u) && reach.has(E.v)) continue
|
||||
forest.add(E)
|
||||
reach.add(E.u).add(E.v)
|
||||
for (const V of this.G.vertices.values()) djs.makeSet(V.id)
|
||||
|
||||
const sortedEdges = edges ?? new BinaryHeap((a, b) => this.G.weights(a) - this.G.weights(b))
|
||||
console.log("a")
|
||||
|
||||
for (const E of sortedEdges) {
|
||||
if (djs.find(E.u.id) !== djs.find(E.v.id)) {
|
||||
forest.add(E)
|
||||
djs.union(E.u.id, E.v.id)
|
||||
}
|
||||
}
|
||||
|
||||
return forest
|
||||
}
|
||||
// const forest = new Set<Edge<vData, eData>>()
|
||||
// /** Keeps track of the reachable vertices. Used to ensure that newly added edges do not create a cycle. */
|
||||
// const reach = new WeakSet<Vertex<vData, eData>>()
|
||||
// const ordered: Iterable<Edge<vData, eData>> = edges ?? this.G.edges.values()
|
||||
|
||||
// for (const E of ordered) {
|
||||
// if (forest.has(E)) continue
|
||||
// if (reach.has(E.u) && reach.has(E.v)) continue
|
||||
// forest.add(E)
|
||||
// reach.add(E.u).add(E.v)
|
||||
// }
|
||||
|
||||
// return forest
|
||||
// }
|
||||
|
||||
/**
|
||||
* Sorts edges by their weight ascending. Useful for speeding up algorithms such as Kruskal's.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
export class DisjointSet<T> {
|
||||
parent: number[]
|
||||
rank: number[]
|
||||
#map: Map<T, number>
|
||||
#mapCount: number
|
||||
|
||||
constructor(elements: Iterable<T> = []) {
|
||||
this.parent = []
|
||||
this.rank = []
|
||||
this.#map = new Map()
|
||||
this.#mapCount = 0
|
||||
|
||||
for (const element of elements) this.makeSet(element)
|
||||
}
|
||||
|
||||
makeSet(element: T) {
|
||||
this.parent.push(this.#mapCount)
|
||||
this.rank.push(0)
|
||||
this.#map.set(element, this.#mapCount)
|
||||
this.#mapCount++
|
||||
}
|
||||
|
||||
find(element: T): number {
|
||||
const index = this.#map.get(element)
|
||||
if (index === undefined) {
|
||||
throw new Error('Element not found')
|
||||
}
|
||||
return this.findIndex(index)
|
||||
}
|
||||
|
||||
private findIndex(index: number): number {
|
||||
if (this.parent[index] !== index) {
|
||||
this.parent[index] = this.findIndex(this.parent[index])
|
||||
}
|
||||
return this.parent[index]
|
||||
}
|
||||
|
||||
union(a: T, b: T) {
|
||||
const aIndex = this.find(a)
|
||||
const bIndex = this.find(b)
|
||||
|
||||
if (aIndex === bIndex) return
|
||||
|
||||
if (this.rank[aIndex] < this.rank[bIndex]) {
|
||||
this.parent[aIndex] = bIndex
|
||||
} else if (this.rank[aIndex] > this.rank[bIndex]) {
|
||||
this.parent[bIndex] = aIndex
|
||||
} else {
|
||||
this.parent[bIndex] = aIndex
|
||||
this.rank[aIndex]++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,17 +28,13 @@ export class BinaryHeap<T> implements Iterable<T> {
|
||||
while (this.length !== 0) yield this.pop()!
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an element into the heap.
|
||||
*/
|
||||
/** Insert an element into the heap. */
|
||||
push(element: T): void {
|
||||
this.#elements.push(element)
|
||||
this.#bubbleUp(this.length - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the minimum element from the heap and return it.
|
||||
*/
|
||||
/** Remove the minimum element from the heap and return it. */
|
||||
pop(): T | undefined {
|
||||
if (this.length === 0) return undefined
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
type Transition<State extends string | number | symbol, InputAlphabet, Context> = (
|
||||
export type Transition<State extends string | number | symbol, InputAlphabet, Context> = (
|
||||
this: StateMachine<State, InputAlphabet, Context>,
|
||||
input: InputAlphabet,
|
||||
machine: StateMachine<State, InputAlphabet, Context>
|
||||
) => void
|
||||
|
||||
class StateMachine<State extends string | number | symbol = 0, InputAlphabet = string, Context = null> {
|
||||
export class StateMachine<State extends string | number | symbol = 0, InputAlphabet = null, Context = null> {
|
||||
#initialState: State
|
||||
#transitionTable: Partial<Record<State, Transition<State, InputAlphabet, Context>>>
|
||||
#acceptStates: Set<State>
|
||||
@@ -61,37 +61,62 @@ if (import.meta.main) {
|
||||
// 0 -> S1
|
||||
// 1 -> S2
|
||||
|
||||
const enum MyState {
|
||||
P = 'P',
|
||||
R = 'R',
|
||||
N = 'N',
|
||||
D = 'D',
|
||||
L = 'L'
|
||||
const enum MarioState {
|
||||
Mario,
|
||||
SuperMario,
|
||||
FireMario,
|
||||
CapeMario,
|
||||
DeadMario
|
||||
}
|
||||
|
||||
const enum Direction {
|
||||
UP = 'UP',
|
||||
DOWN = 'DOWN',
|
||||
const enum MarioStateTransition {
|
||||
Mushroom,
|
||||
FireFlower,
|
||||
Feather,
|
||||
Damage
|
||||
}
|
||||
|
||||
const M = new StateMachine<MyState, Direction>(MyState.P, {
|
||||
[MyState.P]: (input, m) => input === Direction.UP ? m.goto(MyState.P) : m.goto(MyState.R),
|
||||
[MyState.R]: (input, m) => input === Direction.UP ? m.goto(MyState.P) : m.goto(MyState.N),
|
||||
[MyState.N]: (input, m) => input === Direction.UP ? m.goto(MyState.R) : m.goto(MyState.D),
|
||||
[MyState.D]: (input, m) => input === Direction.UP ? m.goto(MyState.N) : m.goto(MyState.L),
|
||||
[MyState.L]: (input, m) => input === Direction.UP ? m.goto(MyState.D) : m.goto(MyState.L),
|
||||
}, [MyState.P])
|
||||
// const M = new StateMachine<MyState, '1' | '0'>(MyState.even, {
|
||||
// [MyState.even]: (input, m) => input === '1' ? m.goto(MyState.even) : m.goto(MyState.odd),
|
||||
// [MyState.odd ]: (input, m) => input === '0' ? m.goto(MyState.even) : m.goto(MyState.odd),
|
||||
// }, [MyState.even])
|
||||
// const M = new StateMachine<MarioState, MarioStateTransition>(
|
||||
// MarioState.Mario,
|
||||
// {
|
||||
// [MarioState.Mario]: (input, m) => {
|
||||
// switch (input) {
|
||||
// case MarioStateTransition.FireFlower: m.goto(MarioState.FireMario); break
|
||||
// case MarioStateTransition.Feather: m.goto(MarioState.CapeMario); break
|
||||
// case MarioStateTransition.Damage: m.goto(MarioState.Mario); break
|
||||
// }
|
||||
// },
|
||||
// [MarioState.SuperMario]: (input, m) => {
|
||||
// switch (input) {
|
||||
// case MarioStateTransition.FireFlower: m.goto(MarioState.FireMario); break
|
||||
// case MarioStateTransition.Feather: m.goto(MarioState.CapeMario); break
|
||||
// case MarioStateTransition.Damage: m.goto(MarioState.Mario); break
|
||||
// }
|
||||
// },
|
||||
// [MarioState.FireMario]: (input, m) => {
|
||||
// switch (input) {
|
||||
// case MarioStateTransition.FireFlower: m.goto(MarioState.FireMario); break
|
||||
// case MarioStateTransition.Feather: m.goto(MarioState.CapeMario); break
|
||||
// case MarioStateTransition.Damage: m.goto(MarioState.Mario); break
|
||||
// }
|
||||
// },
|
||||
// [MarioState.CapeMario]: (input, m) => {
|
||||
// switch (input) {
|
||||
// case MarioStateTransition.FireFlower: m.goto(MarioState.FireMario); break
|
||||
// case MarioStateTransition.Feather: m.goto(MarioState.CapeMario); break
|
||||
// case MarioStateTransition.Damage: m.goto(MarioState.Mario); break
|
||||
// }
|
||||
// },
|
||||
// [MarioState.DeadMario]: () => { }
|
||||
// },
|
||||
// [MarioState.Mario, MarioState.SuperMario, MarioState.FireMario, MarioState.CapeMario]
|
||||
// )
|
||||
|
||||
|
||||
const M = new StateMachine<MarioState, MarioStateTransition>(
|
||||
MarioState.Mario,
|
||||
{},
|
||||
[MarioState.Mario, MarioState.SuperMario, MarioState.FireMario, MarioState.CapeMario]
|
||||
)
|
||||
|
||||
M.runWithInput([
|
||||
Direction.DOWN,
|
||||
Direction.DOWN,
|
||||
Direction.DOWN,
|
||||
])
|
||||
|
||||
console.log(M)
|
||||
console.log(M.accepts())
|
||||
}
|
||||
Reference in new issue
Block a user