Merge pull request #5 from MindfulMinun/next

Next
This commit is contained in:
mindfulminun authored and GitHub committed 2023-12-12 21:36:44 -06:00
commit bfe1a7a05d
11 files changed
+266 -79

No files matched your search

+12 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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')">`)
* // -> `&lt;img src=":(" onerror="alert('xss')"&gt;`
*
* 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 &lt;img src=":(" onerror="alert('xss')"&gt;</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
)
}
+10
View File
@@ -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[],
+2 -6
View File
@@ -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
+3 -1
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
Implement Dijkstra's, Prim and Kruskal's, Topological sort, maybe like Ford-Fulkerson, maybe Christofides
+38 -13
View File
@@ -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.
+53
View File
@@ -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]++
}
}
}
+2 -6
View File
@@ -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
+55 -30
View File
@@ -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())
}