Commit e0150ea9 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
# Example Code
A minimal project giving examples of map-reduce algorithms for 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/map-reduce-examples.git
$ cd map-reduce-examples
```
(If you forget `--recursive` when cloning, you can `cd` into your clone and run
`git submodule update --init --recursive` instead.)
Install dependencies:
```
$ npm install
```
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/map-reduce",
"version": "1.0.0",
"description": "Examples of map-reduce algorithms.",
"type": "module",
"private": true,
"license": "UNLICENSED",
"scripts": {
"lint:js": "eslint --max-warnings 0 ./src",
"lint": "run-s --continue-on-error lint:**",
"test-once:sell": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t \"the sell monoid\"",
"test-once:buySell": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t \"buy/sell monoid\"",
"test-once:parentheses": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t \"parentheses monoid\"",
"test-once:pipes": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t \"pipes monoid\"",
"test-once:indexOfMinimum": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t \"index-of-minimum monoid\"",
"test-once:strings": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage -t \"strings 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."
]
}
// A map-reduce algorithm implementing the solution to Question 2 from the
// recitation at <https://canvas.unl.edu/courses/114253/files/10772858>.
class MonoidElement {
constructor(buyPrice, sellPrice, minimumPrice, maximumPrice) {
this.buyPrice = buyPrice;
this.sellPrice = sellPrice;
this.minimumPrice = minimumPrice;
this.maximumPrice = maximumPrice;
}
get profit() {
return this.sellPrice - this.buyPrice;
}
}
export const IDENTITY_ELEMENT = new MonoidElement(Infinity, -Infinity, Infinity, -Infinity);
export function encodeAsMonoidElement(price) {
return new MonoidElement(price, price, price, price);
}
export function combineMonoidElements(left, right) {
const minimumPrice = Math.min(left.minimumPrice, right.minimumPrice);
const maximumPrice = Math.max(left.maximumPrice, right.maximumPrice);
const buyAndSellOnLeft = new MonoidElement(
left.buyPrice,
left.sellPrice,
minimumPrice,
maximumPrice,
);
let best = buyAndSellOnLeft;
const buyOnLeftAndSellOnRight = new MonoidElement(
left.minimumPrice,
right.maximumPrice,
minimumPrice,
maximumPrice,
);
if (buyOnLeftAndSellOnRight.profit > best.profit) {
best = buyOnLeftAndSellOnRight;
}
const buyAndSellOnRight = new MonoidElement(
right.buyPrice,
right.sellPrice,
minimumPrice,
maximumPrice,
);
if (buyAndSellOnRight.profit > best.profit) {
best = buyAndSellOnRight;
}
return best;
}
/* eslint-disable no-magic-numbers */
import { parallelMapReduce } from './mapReduce.js';
describe('map-reduce with the buy/sell monoid', () => {
test('finds the optimal buy and sell prices from the example forecast with one thread', async() => {
const result = await parallelMapReduce('./buySell.js', [103, 101, 102, 100], 1);
expect(result.buyPrice).toBe(101);
expect(result.sellPrice).toBe(102);
});
test('finds the optimal buy and sell prices from the example forecast with two threads', async() => {
const result = await parallelMapReduce('./buySell.js', [103, 101, 102, 100], 2);
expect(result.buyPrice).toBe(101);
expect(result.sellPrice).toBe(102);
});
test('finds the optimal buy and sell prices from the example forecast with three threads', async() => {
const result = await parallelMapReduce('./buySell.js', [103, 101, 102, 100], 3);
expect(result.buyPrice).toBe(101);
expect(result.sellPrice).toBe(102);
});
});
// A map-reduce algorithm implementing the solution to Question 2 from the
// recitation at <https://canvas.unl.edu/courses/114253/files/10850616>.
class MonoidElement {
constructor(indexOfMinimum, count, valueOfMinimum) {
this.indexOfMinimum = indexOfMinimum;
this.count = count;
this.valueOfMinimum = valueOfMinimum;
}
}
export const IDENTITY_ELEMENT = new MonoidElement(0, 0, Infinity);
export function encodeAsMonoidElement(element) {
return new MonoidElement(0, 1, element);
}
export function combineMonoidElements(left, right) {
if (left.valueOfMinimum <= right.valueOfMinimum) {
return new MonoidElement(left.indexOfMinimum, left.count + right.count, left.valueOfMinimum);
}
return new MonoidElement(left.count + right.indexOfMinimum, left.count + right.count, right.valueOfMinimum);
}
/* eslint-disable no-magic-numbers */
import { parallelMapReduce } from './mapReduce.js';
describe('map-reduce with the index-of-minimum monoid', () => {
test('finds the index of the minimum of the example sequence with one thread', async() => {
expect((await parallelMapReduce('./indexOfMinimum.js', [4, 2, 3, 4, 2], 1)).indexOfMinimum).toBe(1);
});
test('finds the index of the minimum of the example sequence with two threads', async() => {
expect((await parallelMapReduce('./indexOfMinimum.js', [4, 2, 3, 4, 2], 2)).indexOfMinimum).toBe(1);
});
test('finds the index of the minimum of the example sequence with three threads', async() => {
expect((await parallelMapReduce('./indexOfMinimum.js', [4, 2, 3, 4, 2], 3)).indexOfMinimum).toBe(1);
});
});
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;
}
// A map-reduce algorithm implementing the parentheses monoid from class.
class MonoidElement {
constructor(closeParenthesesCount, openParenthesesCount) {
this.closeParenthesesCount = closeParenthesesCount;
this.openParenthesesCount = openParenthesesCount;
}
get balanced() {
return this.closeParenthesesCount === 0 && this.openParenthesesCount === 0;
}
}
export const IDENTITY_ELEMENT = new MonoidElement(0, 0);
export function encodeAsMonoidElement(character) {
switch (character) {
case ')':
return new MonoidElement(1, 0);
case '(':
return new MonoidElement(0, 1);
default:
return IDENTITY_ELEMENT;
}
}
export function combineMonoidElements(left, right) {
const cancellations = Math.min(left.openParenthesesCount, right.closeParenthesesCount);
return new MonoidElement(
left.closeParenthesesCount + right.closeParenthesesCount - cancellations,
left.openParenthesesCount + right.openParenthesesCount - cancellations,
);
}
/* eslint-disable no-magic-numbers */
import { parallelMapReduce } from './mapReduce.js';
describe('map-reduce with the parentheses monoid', () => {
test('summarizes the example balanced string with one thread', async() => {
expect((await parallelMapReduce('./parentheses.js', '()(())', 1)).balanced).toBe(true);
});
test('summarizes the example balanced string with two threads', async() => {
expect((await parallelMapReduce('./parentheses.js', '()(())', 2)).balanced).toBe(true);
});
test('summarizes the example balanced string with three threads', async() => {
expect((await parallelMapReduce('./parentheses.js', '()(())', 3)).balanced).toBe(true);
});
test('summarizes the example imbalanced string with one thread', async() => {
expect((await parallelMapReduce('./parentheses.js', '())))(()', 1)).balanced).toBe(false);
});
test('summarizes the example imbalanced string with two threads', async() => {
expect((await parallelMapReduce('./parentheses.js', '())))(()', 1)).balanced).toBe(false);
});
test('summarizes the example imbalanced string with three threads', async() => {
expect((await parallelMapReduce('./parentheses.js', '())))(()', 1)).balanced).toBe(false);
});
});
// A map-reduce algorithm implementing the solution to Question 1 from the
// recitation at <https://canvas.unl.edu/courses/114253/files/10850616>.
class MonoidElement {
constructor(warningCounts, reversesSampling) {
this.warningCounts = warningCounts;
this.reversesSampling = reversesSampling;
}
}
export const IDENTITY_ELEMENT = new MonoidElement([0, 0], false);
export function encodeAsMonoidElement(logMessage) {
switch (logMessage) {
case 'X':
return new MonoidElement([0, 0], true);
case 'Y':
return new MonoidElement([1, 0], false);
default:
return IDENTITY_ELEMENT;
}
}
export function combineMonoidElements(left, right) {
const adjustedRightWarningCounts = left.reversesSampling ? [...right.warningCounts].reverse() : right.warningCounts;
const totals = [];
for (let i = 0; i < 2; ++i) {
totals.push(left.warningCounts[i] + adjustedRightWarningCounts[i]);
}
return new MonoidElement(totals, left.reversesSampling ^ right.reversesSampling);
}
/* eslint-disable no-magic-numbers */
import { parallelMapReduce } from './mapReduce.js';
describe('map-reduce with the pipes monoid', () => {
test('summarizes the example log with one thread', async() => {
expect((await parallelMapReduce('./pipes.js', 'XYYXY', 1)).warningCounts).toEqual([1, 2]);
});
test('summarizes the example log with two threads', async() => {
expect((await parallelMapReduce('./pipes.js', 'XYYXY', 2)).warningCounts).toEqual([1, 2]);
});
test('summarizes the example log with three threads', async() => {
expect((await parallelMapReduce('./pipes.js', 'XYYXY', 3)).warningCounts).toEqual([1, 2]);
});
});
// A map-reduce algorithm implementing the solution to Question 1 from the
// recitation at <https://canvas.unl.edu/courses/114253/files/10772858>.
class MonoidElement {
constructor(maximumCumulativeChange, totalChange) {
this.maximumCumulativeChange = maximumCumulativeChange;
this.totalChange = totalChange;
}
}
export const IDENTITY_ELEMENT = new MonoidElement(-Infinity, 0);
export function encodeAsMonoidElement(change) {
return new MonoidElement(Math.max(change, 0), change);
}
export function combineMonoidElements(left, right) {
return new MonoidElement(
Math.max(
left.maximumCumulativeChange,
left.totalChange + right.maximumCumulativeChange,
),
left.totalChange + right.totalChange,
);
}
/* eslint-disable no-magic-numbers */
import { parallelMapReduce } from './mapReduce.js';
describe('map-reduce with the sell monoid', () => {
test('finds the maximum cumulative change from the example forecast with one thread', async() => {
expect((await parallelMapReduce('./sell.js', [-1, 4, -3, 2], 1)).maximumCumulativeChange).toBe(3);
});
test('finds the maximum cumulative change from the example forecast with one thread', async() => {
expect((await parallelMapReduce('./sell.js', [-1, 4, -3, 2], 2)).maximumCumulativeChange).toBe(3);
});
test('finds the maximum cumulative change from the example forecast with one thread', async() => {
expect((await parallelMapReduce('./sell.js', [-1, 4, -3, 2], 3)).maximumCumulativeChange).toBe(3);
});
});
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment