Commit 237cc1d8 authored by Brady James Garvin's avatar Brady James Garvin
Browse files

Initial commit.

parents
# 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:csce-310/eslint-config.git
# Tests-Only Starter Code
A minimal project to be used as starter code for Homework 4 in the 250 section
of the CSCE 310 course at UNL.
# Quick Start
Recursively clone this repository and `cd` into the root folder:
```
$ git clone --recursive git@git.unl.edu:csce-310/2021-fall-homework-4.git
$ cd 2021-fall-homework-4
```
(If you forget `--recursive` when cloning, you can `cd` into your clone and run
`git submodule update --init --recursive` instead.)
Install dependencies:
```
$ npm install
```
# Instructions
See <https://canvas.unl.edu/courses/114253/assignments/1110043>.
Subproject commit 24df42fb655d234b83c93b0fb24d012e4d9ecb58
{
"folders": [
{
"path": "."
}
],
"settings": {
"files.eol": "LF",
"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/homework-4-implementation",
"version": "1.0.0",
"description": "A minimal project to be used as starter code for Homework 4.",
"type": "module",
"private": true,
"license": "UNLICENSED",
"scripts": {
"lint:js": "eslint --max-warnings 0 ./src",
"lint": "run-s --continue-on-error lint:**",
"test-once:sum": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t 'addition monoid'",
"test-once:program": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t 'program monoid'",
"test-once:comments": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t 'comment monoid'",
"test-once:orientation": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t 'orientation monoid'",
"test-once:longestString": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t 'longest-string monoid'",
"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": "^7.30.0",
"jest": "^27.0.6",
"jest-environment-node": "^27.0.6"
},
"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."
]
}
// INSTRUCTIONS: In many programming languages based on C, comments come in two
// flavors. A single-line comment begins with two slashes and continues until
// the end of the line:
//
// > this is code // this is a comment
// > this is code again because the comment above ended with the newline
//
// And a multi-line comment begins with a slash-star and continues until the
// next star-slash:
//
// > this is code /* this is a comment, and
// > this is still the same comment,
// > but */ this is code again because the comment ended with the star-slash.
//
// Suppose that you designing an algorithm to check large pieces of source code
// and make sure that they do not end in the middle of a comment. The best
// possible serial algorithm already runs in linear time, but the check can be
// made even faster for large inputs by using a parallel algorithm, in
// particular a map-reduce algorithm.
//
// A basic map-reduce framework is already provided in `mapReduce.js` and
// `worker.js`. For the functional correctness points, finish the algorithm by
// implementing the following features below:
//
// * Determine the fields that you would need to store in each monoid element
// and implement an appropriate constructor for the `MonoidElement` class.
//
// * Determine the identity element of your monoid, and store it in the constant
// `IDENTITY_ELEMENT`.
//
// * Determine how to convert individual characters from the source code your
// algorithm is checking to monoid elements (the "map" step), and implement
// that conversion in the function `encodeAsMonoidElement`.
//
// * Determine how to combine two monoid elements (the "reduce" step), and
// implement that combination process in the function `combineMonoidElements`.
//
// * Determine how to covert the final monoid element to a boolean (`true` if
// the source code is okay but `false` if the source code ends in the middle
// of a comment) and implement that conversion process in the getter `get
// okay`.
//
// (There is an example monoid that you can mimic implemented in `sum.js`; its
// test cases are in `sum.test.js`.)
//
// As a hint for finding an appropriate monoid, properly commented programs can
// be recognized with a finite state machine (FSM), so, like in class, try
// drawing the FSM and then deriving the syntactic monoid.
//
// Note that the test case inputs are so small that the cost of creating
// threads, communicating between them, and then destroying threads almost
// certainly outweighs the benefits of parallelism, which is why the tests may
// feel a little slow. You would need much larger inputs to see the benefits of
// multithreading.
//
// Finally, note that Jest is only configured to collect coverage from the main
// thread. Therefore the code coverage report from the test suite might show
// that some lines of code are not covered if they only ran on a worker thread;
// you can safely ignore those warnings.
// INSTRUCTIONS: Uncomment and implement this class's constructor and
// boolean-valued getter.
class MonoidElement {
// constructor() {
// }
// get okay() {
// return false;
// }
}
// INSTRUCTIONS: Set this constant to a proper identity element.
export const IDENTITY_ELEMENT = new MonoidElement();
// INSTRUCTIONS: Implement this function so that it converts a character from
// the program source code to an appropriate monoid element.
export function encodeAsMonoidElement(character) {
return new MonoidElement();
}
// INSTRUCTIONS: Implement this function so that it combines two monoid
// elements.
export function combineMonoidElements(left, right) {
return new MonoidElement();
}
This diff is collapsed.
export const ASSIGN = '=';
export const ADD = '+=';
export const SUBTRACT = '-=';
export const MULTIPLY = '*=';
export const DIVIDE = '/=';
export class Instruction {
constructor(operation, value) {
this.operation = operation;
this.value = value;
}
}
// INSTRUCTIONS: Suppose that you have files where many strings are stored,
// delimited by commas, and you are designing an algorithm to find the length of
// the longest string in a file. The best possible serial algorithm for this
// problem already runs in linear time, but the process can be made even faster
// for large files by using a parallel algorithm, in particular a map-reduce
// algorithm.
//
// A basic map-reduce framework is already provided in `mapReduce.js` and
// `worker.js`. For the extra credit, finish the algorithm by implementing the
// following features below:
//
// * Determine the fields that you would need to store in each monoid element
// and implement an appropriate constructor for the `MonoidElement` class.
// Make sure that one of your fields is `maximumLength`, which is the field
// that the test cases will be checking to see if it holds the length of the
// longest string.
//
// * Determine the identity element of your monoid, and store it in the constant
// `IDENTITY_ELEMENT`.
//
// * Determine how to convert characters from the file to monoid elements (the
// "map" step), and implement that conversion in the function
// `encodeAsMonoidElement`.
//
// * Determine how to combine two monoid elements (the "reduce" step), and
// implement that combination process in the function `combineMonoidElements`.
//
// (There is an example monoid that you can mimic implemented in `sum.js`; its
// test cases are in `sum.test.js`.)
//
// Note that the test case inputs are so small that the cost of creating
// threads, communicating between them, and then destroying threads almost
// certainly outweighs the benefits of parallelism, which is why the tests may
// feel a little slow. You would need much larger files to see the benefits of
// multithreading.
//
// Finally, note that Jest is only configured to collect coverage from the main
// thread. Therefore the code coverage report from the test suite might show
// that some lines of code are not covered if they only ran on a worker thread;
// you can safely ignore those warnings.
class MonoidElement {
// constructor() {
// }
}
export const IDENTITY_ELEMENT = new MonoidElement();
export function encodeAsMonoidElement(character) {
return new MonoidElement();
}
export function combineMonoidElements(left, right) {
return new MonoidElement();
}
This diff is collapsed.
import { Worker } from 'worker_threads';
class ModuleWorker extends Worker {
constructor(path) {
const code = `import '${new URL(path, import.meta.url).href}';`;
super(new URL(`data:text/javascript,${encodeURIComponent(code)}`));
}
dispatch(data) {
this.postMessage(data);
return new Promise((resolve) => this.once('message', resolve));
}
}
function iterativeReduce(identityElement, combineMonoidElements, sequence) {
let result = identityElement;
for (const element of sequence) {
result = combineMonoidElements(result, element);
}
return result;
}
export async function parallelMapReduce(proceduresURL, sequence, workerCount) {
console.assert(workerCount > 0, `Cannot distribute work to a nonpositive (${workerCount}) number of threads.`);
const {
IDENTITY_ELEMENT,
combineMonoidElements,
} = await import(proceduresURL);
const workers = [...Array(workerCount)].map(() => new ModuleWorker('./worker.js'));
const jobs = workers.map((worker, workerIndex) => {
const low = Math.floor(workerIndex * sequence.length / workerCount);
const high = Math.floor((workerIndex + 1) * sequence.length / workerCount);
return worker.dispatch({
proceduresURL,
data: [...sequence.slice(low, high)],
});
});
const result = iterativeReduce(IDENTITY_ELEMENT, combineMonoidElements, await Promise.all(jobs));
for (const worker of workers) {
worker.terminate();
}
return result;
}
// INSTRUCTIONS: A rover communicating wirelessly with a base station may have
// its connection weakened or interrupted as it changes orientation, so,
// depending on the headings it expects to use during a traverse, it may prefer
// a different base station to maintain a better connection.
//
// Suppose that you are designing an algorithm to analyze a planned traverse to
// determine the rover's most common heading, and, for the time being, you are
// making some assumptions to keep to problem easy: the rover starts out facing
// north, it has only three steering commands:
//
// * `f` means "forward", keeping the same heading for one minute,
// * `l` means "left", turning 90° to the left for one minute, and
// * `r` means "right", turning 90° to the right for one minute,
//
// and you only need to consider headings at the beginning of each steering
// command. For example, if the traverse's steering commands are
//
// > `lfr`
//
// Then the rover is facing north at the beginning of the `l` command and west
// at the beginning of the other two commands, so its most common heading during
// the traverse is west.
//
// The best possible serial algorithm for this problem already runs in linear
// time, but the process can be made even faster for long traverses by using a
// parallel algorithm, in particular a map-reduce algorithm.
//
// A basic map-reduce framework is already provided in `mapReduce.js` and
// `worker.js`. For the functional correctness points, finish the algorithm by
// implementing the following features below:
//
// * Determine the fields that you would need to store in each monoid element
// and implement an appropriate constructor for the `MonoidElement` class.
//
// * Determine the identity element of your monoid, and store it in the constant
// `IDENTITY_ELEMENT`.
//
// * Determine how to convert steering commands to monoid elements (the "map"
// step), and implement that conversion in the function
// `encodeAsMonoidElement`.
//
// * Determine how to combine two monoid elements (the "reduce" step), and
// implement that combination process in the function `combineMonoidElements`.
//
// * Determine how to get the most common heading from the final monoid element
// and implement that process in the method `run`. The test cases will expect
// you to represent directions as numbers; see the constants at the top of
// `orientation.test.js`.
//
// (There is an example monoid that you can mimic implemented in `sum.js`; its
// test cases are in `sum.test.js`.)
//
// As a hint for finding an appropriate monoid, look for other monoids from
// class and/or recitation that counted how often something happened and imitate
// their design here.
//
// Note that the test case inputs are so small that the cost of creating
// threads, communicating between them, and then destroying threads almost
// certainly outweighs the benefits of parallelism, which is why the tests may
// feel a little slow. You would need much larger blocks to see the benefits of
// multithreading.
//
// Finally, note that Jest is only configured to collect coverage from the main
// thread. Therefore the code coverage report from the test suite might show
// that some lines of code are not covered if they only ran on a worker thread;
// you can safely ignore those warnings.
// This function acts like the mod operator from math rather than the `%`
// operator, which you may find useful.
// eslint-disable-next-line no-unused-vars
function mod(value, modulus) {
const signed = value % modulus;
return signed < 0 ? signed + modulus : signed;
}
// INSTRUCTIONS: Uncomment and implement this class's constructor and `get
// mostCommonOrientation` getter.
class MonoidElement {
// constructor() {
// }
// get mostCommonOrientation() {
// return 0;
// }
}
// INSTRUCTIONS: Set this constant to a proper identity element.
export const IDENTITY_ELEMENT = new MonoidElement();
// INSTRUCTIONS: Implement this function so that it converts a steering commands
// to an appropriate monoid element.
export function encodeAsMonoidElement(steeringCommand) {
return new MonoidElement();
}
// INSTRUCTIONS: Implement this function so that it combines two monoid
// elements.
export function combineMonoidElements(left, right) {
return new MonoidElement();
}
/* eslint-disable no-magic-numbers */
import { parallelMapReduce } from './mapReduce.js';
const NORTH = 0;
const EAST = 1;
const SOUTH = 2;
const WEST = 3;
describe('map-reduce with the orientation monoid', () => {
test('finds north as most common after one wait with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'f', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds north as most common after two waits with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'ff', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds north as most common after three waits with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'fff', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds west as most common after a left rotation and two waits with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lff', 1)).mostCommonOrientation).toBe(WEST);
});
test('finds west as most common after a left rotation and three waits with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lfff', 1)).mostCommonOrientation).toBe(WEST);
});
test('finds east as most common after a right rotation and two waits with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'rff', 1)).mostCommonOrientation).toBe(EAST);
});
test('finds east as most common after a right rotation and three waits with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'rfff', 1)).mostCommonOrientation).toBe(EAST);
});
test('finds south as most common after a double left rotation and two waits with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'llff', 1)).mostCommonOrientation).toBe(SOUTH);
});
test('finds south as most common after a double right rotation and two waits with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'rrff', 1)).mostCommonOrientation).toBe(SOUTH);
});
test('finds north as most common after a reverted left rotation and one wait with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lrf', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds north as most common after a reverted right rotation and one wait with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'rlf', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds north as most common after one wait with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'f', 2)).mostCommonOrientation).toBe(NORTH);
});
test('finds north as most common after two waits with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'ff', 2)).mostCommonOrientation).toBe(NORTH);
});
test('finds north as most common after three waits with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'fff', 2)).mostCommonOrientation).toBe(NORTH);
});
test('finds west as most common after a left rotation and two waits with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'lff', 2)).mostCommonOrientation).toBe(WEST);
});
test('finds west as most common after a left rotation and three waits with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'lfff', 2)).mostCommonOrientation).toBe(WEST);
});
test('finds east as most common after a right rotation and two waits with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'rff', 2)).mostCommonOrientation).toBe(EAST);
});
test('finds east as most common after a right rotation and three waits with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'rfff', 2)).mostCommonOrientation).toBe(EAST);
});
test('finds south as most common after a double left rotation and two waits with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'llff', 2)).mostCommonOrientation).toBe(SOUTH);
});
test('finds south as most common after a double right rotation and two waits with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'rrff', 2)).mostCommonOrientation).toBe(SOUTH);
});
test('finds north as most common after a reverted left rotation and one wait with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'lrf', 2)).mostCommonOrientation).toBe(NORTH);
});
test('finds north as most common after a reverted right rotation and one wait with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'rlf', 2)).mostCommonOrientation).toBe(NORTH);
});
test('finds the most common heading from example rotation sequence 0 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'rfffflrlfrlrllrr', 1)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 1 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lffffflfllllfrrl', 1)).mostCommonOrientation).toBe(WEST);
});
test('finds the most common heading from example rotation sequence 2 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lfrfllrffrlfrrrf', 1)).mostCommonOrientation).toBe(WEST);
});
test('finds the most common heading from example rotation sequence 3 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'frfffrrfllllllrl', 1)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 4 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'ffllfrlfrlflllrr', 1)).mostCommonOrientation).toBe(SOUTH);
});
test('finds the most common heading from example rotation sequence 5 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lrlfllffrlfflrff', 1)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 6 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'frffflrrflfrrlrr', 1)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 7 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'rflrfffflflrlllf', 1)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 8 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'rflrffrlrlflrllf', 1)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 9 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'ffffffrlrlrrllrf', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds the most common heading from example rotation sequence 10 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lflflffrlrfflrrr', 1)).mostCommonOrientation).toBe(SOUTH);
});
test('finds the most common heading from example rotation sequence 11 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'fllrrrrrrfrflfrl', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds the most common heading from example rotation sequence 12 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lfffflrfrlrllrll', 1)).mostCommonOrientation).toBe(WEST);
});
test('finds the most common heading from example rotation sequence 13 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'flrfllrlllrlllll', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds the most common heading from example rotation sequence 14 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'frllfffrlfffffrl', 1)).mostCommonOrientation).toBe(WEST);
});
test('finds the most common heading from example rotation sequence 15 with one thread', async() => {
expect((await parallelMapReduce('./orientation.js', 'lrffrfllfrrrrllf', 1)).mostCommonOrientation).toBe(NORTH);
});
test('finds the most common heading from example rotation sequence 0 with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'rfffflrlfrlrllrr', 2)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 1 with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'lffffflfllllfrrl', 2)).mostCommonOrientation).toBe(WEST);
});
test('finds the most common heading from example rotation sequence 2 with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'lfrfllrffrlfrrrf', 2)).mostCommonOrientation).toBe(WEST);
});
test('finds the most common heading from example rotation sequence 3 with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'frfffrrfllllllrl', 2)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 4 with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'ffllfrlfrlflllrr', 2)).mostCommonOrientation).toBe(SOUTH);
});
test('finds the most common heading from example rotation sequence 5 with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'lrlfllffrlfflrff', 2)).mostCommonOrientation).toBe(EAST);
});
test('finds the most common heading from example rotation sequence 6 with two threads', async() => {
expect((await parallelMapReduce('./orientation.js', 'frffflrrflfrrlrr', 2)).mostCommonOrientation).toBe(EAST);