Skip to content
Snippets Groups Projects
Commit ed68a3b8 authored by Brady James Garvin's avatar Brady James Garvin
Browse files

Initial commit.

parents
Branches main
No related tags found
No related merge requests found
# Disable line-ending conversions for this repository.
* -text
# dependencies
/node_modules
# testing
/coverage
# production
/build
# environments
.env.local
.env.development.local
.env.test.local
.env.production.local
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# misc
*~
.DS_Store
[submodule "eslint-config"]
path = eslint-config
url = git@git.unl.edu:soft-core/soft-260/eslint-config.git
Subproject commit a85278e2bd62f637800bc32fa6b372bc2855546e
{
"folders": [
{
"path": "."
}
],
"settings": {
"files.eol": "\n",
"files.exclude": {
"**/node_modules": true
},
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true
}
}
# dependencies
/node_modules
# testing
/coverage
# production
/build
# environments
.env.local
.env.development.local
.env.test.local
.env.production.local
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# misc
*~
.DS_Store
This diff is collapsed.
{
"name": "@unlsoft/leafy-trees",
"version": "1.0.0",
"description": "Starter code for the lab on leafy trees.",
"type": "module",
"private": true,
"license": "UNLICENSED",
"scripts": {
"lint:js": "eslint --max-warnings 0 ./src",
"lint": "run-s --continue-on-error lint:**",
"test-once:leafyTreeExperiment": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t \"an ordered leafy tree\"",
"test-once:conversion": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t \"the constructor and the toString method\"",
"test-once:indexing": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t \"the get method\"",
"test-once:slicing": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t \"the slice method\"",
"test-once:splicing": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t \"the splice method\"",
"test-once": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watchAll --coverage"
},
"dependencies": {
"npm-run-all": "^4.1.5"
},
"devDependencies": {
"@unlsoft/eslint-config": "file:../eslint-config",
"eslint": "^8.20.0",
"jest": "^29.5.0",
"jest-environment-node": "^29.5.0"
},
"eslintConfig": {
"extends": "@unlsoft"
},
"jest": {
"clearMocks": true,
"collectCoverageFrom": [
"src/**/*.js",
"!src/testing/**/*.js"
],
"resetMocks": false,
"restoreMocks": false,
"testEnvironment": "./src/testing/failFast.js",
"transform": {}
},
"//": [
"See https://github.com/facebook/jest/issues/9430 for information on Jest+ES6."
]
}
export class MonoidalAnnotation {
constructor(identity, convert, combine, compare) {
this.identity = identity;
this.convert = convert;
this.combine = combine;
this.compare = compare;
}
}
class Node {
constructor(tree, parent) {
this.tree = tree;
this.parent = parent;
}
}
class Branch extends Node {
constructor(tree, parent, children) {
super(tree, parent);
this.children = children;
if (parent === undefined) {
this.tree.root = this;
} else {
for (const [index, candidate] of parent.children.entries()) {
if (children.includes(candidate)) {
parent.children[index] = this;
}
}
}
for (const child of children) {
child.parent = this;
}
this.update();
}
updateWithoutRecursion() {
console.assert(this.children.length === 2);
const [left, right] = this.children;
this.height = Math.max(left.height, right.height) + 1;
this.summary = this.tree.annotation.combine(left.summary, right.summary);
}
replaceWith(child) {
console.assert(this.children.includes(child));
if (this.parent === undefined) {
this.tree.root = child;
child.parent = undefined;
} else {
this.parent.children[this.parent.children.indexOf(this)] = child;
child.parent = this.parent;
}
}
rotateLeft() {
console.assert(this.children.length === 2);
const child = this.children[1];
console.assert(child.children.length === 2);
const grandchild = child.children[0];
this.replaceWith(child);
child.children[0] = this;
this.parent = child;
this.children[1] = grandchild;
grandchild.parent = this;
this.updateWithoutRecursion();
}
rotateRight() {
console.assert(this.children.length === 2);
const child = this.children[0];
console.assert(child.children.length === 2);
const grandchild = child.children[1];
this.replaceWith(child);
child.children[1] = this;
this.parent = child;
this.children[0] = grandchild;
grandchild.parent = this;
this.updateWithoutRecursion();
}
update() {
if (this.children.length === 1) {
this.replaceWith(this.children[0]);
} else {
console.assert(this.children.length === 2);
const [left, right] = this.children;
if (left.height < right.height - 1) {
console.assert(right.children.length === 2);
if (right.children[1].height < right.height - 1) {
right.rotateRight();
}
this.rotateLeft();
} else if (right.height < left.height - 1) {
console.assert(left.children.length === 2);
if (left.children[0].height < left.height - 1) {
left.rotateLeft();
}
this.rotateRight();
} else {
this.updateWithoutRecursion();
}
}
if (this.parent !== undefined) {
this.parent.update();
}
}
get leftmost() {
console.assert(this.children.length === 2);
return this.children[0].leftmost;
}
get rightmost() {
console.assert(this.children.length === 2);
return this.children[0].rightmost;
}
find(position, accumulation) {
console.assert(this.children.length === 2);
const candidateAccumulation = this.tree.annotation.combine(accumulation, this.children[0].summary);
if (this.tree.annotation.compare(position, candidateAccumulation) < 0) {
return this.children[0].find(position, accumulation);
}
return this.children[1].find(position, candidateAccumulation);
}
toString() {
const children = this.children.map((child) => `${child},`.replaceAll(/^/gum, ' ')).join('\n');
return `${this.summary} {\n${children}\n}`;
}
}
class Leaf extends Node {
constructor(tree, element) {
super(tree, undefined);
this.element = element;
this.summary = tree.annotation.convert(element);
}
get successor() {
let [child, current] = [this, this.parent];
while (current !== undefined) {
console.assert(current.children.length === 2);
if (current.children[0] === child) {
current = current.children[1];
while (current instanceof Branch) {
current = current.children[0];
}
return current;
}
[child, current] = [current, current.parent];
}
return undefined;
}
get height() {
return 0;
}
get leftmost() {
return this;
}
get rightmost() {
return this;
}
find(position, accumulation) {
return [this, accumulation];
}
get(position, accumulation) {
if (this.tree.annotation.compare(position, accumulation) !== 0) {
return undefined;
}
return this.element;
}
add(position, element, accumulation) {
const sibling = new Leaf(this.tree, element);
// eslint-disable-next-line no-new -- false positive; the branch is actually linked to other objects
new Branch(
this.tree,
this.parent,
this.tree.annotation.compare(position, this.tree.annotation.combine(accumulation, this.summary)) < 0 ?
[sibling, this] :
[this, sibling],
);
return sibling;
}
delete(position, accumulation) {
if (this.tree.annotation.compare(position, accumulation) !== 0) {
return undefined;
}
if (this.parent === undefined) {
this.tree.root = undefined;
} else {
this.parent.children.splice(this.parent.children.indexOf(this), 1);
this.parent.update();
}
return this.element;
}
toString() {
return `${this.summary} from ${this.element}`;
}
}
export class OrderedLeafyTree {
constructor(annotation) {
this.annotation = annotation;
this.root = undefined;
}
find(position) {
if (this.root === undefined) {
return [undefined, this.annotation.identity];
}
return this.root.find(position, this.annotation.identity);
}
get(position) {
const [leaf, accumulation] = this.find(position);
return leaf?.get(position, accumulation);
}
*[Symbol.iterator]() {
let current = this.root?.leftmost;
while (current !== undefined) {
yield current.element;
current = current.successor;
}
}
*subrange(begin, end) {
let [current, accumulation] = this.find(begin);
if (this.annotation.compare(accumulation, begin) < 0) {
return;
}
while (current !== undefined) {
accumulation = this.annotation.combine(accumulation, current.summary);
if (this.annotation.compare(accumulation, end) > 0) {
return;
}
yield current.element;
current = current.successor;
}
}
add(position, element) {
if (this.root === undefined) {
this.root = new Leaf(this, element);
return this.root;
}
const [leaf, accumulation] = this.find(position);
console.assert(leaf !== undefined);
return leaf.add(position, element, accumulation);
}
delete(position) {
if (this.root === undefined) {
return undefined;
}
const [leaf, accumulation] = this.find(position);
console.assert(leaf !== undefined);
return leaf.delete(position, accumulation);
}
toString() {
return this.root === undefined ? '[empty tree]' : `${this.root}`;
}
}
import { MonoidalAnnotation, OrderedLeafyTree } from './orderedLeafyTree.js';
describe('an ordered leafy tree', () => {
test('takes this shape when built with an example monoid', () => {
const ANNOTATION = new MonoidalAnnotation(
0,
(element) => element,
(left, right) => left + right,
(left, right) => left - right,
);
const tree = new OrderedLeafyTree(ANNOTATION);
tree.add(5, 5);
console.log(`${tree}`);
tree.add(2, 2);
console.log(`${tree}`);
tree.add(6, 6);
console.log(`${tree}`);
tree.add(8, 8);
console.log(`${tree}`);
});
});
import { MonoidalAnnotation, OrderedLeafyTree } from './orderedLeafyTree.js';
const ROPE_ANNOTATION = new MonoidalAnnotation(
0, // TODO: Implement this identity element.
(element) => 0, // TODO: Implement this map step.
(left, right) => 0, // TODO: Implement this combine step.
(left, right) => 0, // TODO: Implement this comparator.
);
export class Rope {
constructor(string) {
this.tree = new OrderedLeafyTree(ROPE_ANNOTATION);
this.splice(0, 0, string);
}
get(index) {
return undefined; // TODO: Implement this method.
}
slice(begin, end) {
// TODO: Implement this method.
}
splice(begin, deleteCount, newText) {
// TODO: Implement this method.
}
toString() {
let result = '';
for (const character of this.tree) {
result += character;
}
return result;
}
}
This diff is collapsed.
import jestEnvironmentNode from 'jest-environment-node';
export default class FailFastEnvironment extends jestEnvironmentNode.default {
constructor(...rest) {
super(...rest);
this.failed = false;
}
async handleTestEvent(event, state) {
switch (event.name) {
case 'hook_failure':
case 'test_fn_failure':
this.failed = true;
break;
case 'test_start':
if (this.failed) {
event.test.mode = 'skip';
}
break;
default:
}
if (super.handleTestEvent !== undefined) {
await super.handleTestEvent(event, state);
}
}
}
This diff is collapsed.
{
"name": "@unlsoft/leafy-trees-lab",
"version": "1.0.0",
"description": "Starter code for the lab on leafy trees.",
"private": true,
"license": "UNLICENSED",
"scripts": {
"postinstall:eslint-config": "cd eslint-config && npm install",
"postinstall:app": "cd leafy-trees && npm install",
"postinstall": "run-s postinstall:**",
"lint:app": "cd leafy-trees && npm run lint",
"lint": "run-s --continue-on-error lint:**",
"test-once:leafyTreeExperiment": "cd homework-4 && npm run test-once:leafyTreeExperiment",
"test-once:conversion": "cd homework-4 && npm run test-once:conversion",
"test-once:indexing": "cd homework-4 && npm run test-once:indexing",
"test-once:slicing": "cd homework-4 && npm run test-once:slicing",
"test-once:splicing": "cd homework-4 && npm run test-once:splicing",
"test-once": "cd homework-4 && npm run test-once",
"test": "run-s test-once"
},
"devDependencies": {
"ghooks": "^2.0.4",
"npm-run-all": "^4.1.5"
},
"config": {
"ghooks": {
"pre-commit": "npm run lint"
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment