Commit cd760a2c 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 "stylelint-config"]
path = stylelint-config
url = git@git.unl.edu:soft-core/soft-260/stylelint-config.git
[submodule "eslint-config"]
path = eslint-config
url = git@git.unl.edu:soft-core/soft-260/eslint-config.git
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
# Graph Search vs. Dynamic Programming vs Greedy Algorithms
* Idea behind *graph search* design:
* Frame the problem as a problem on a graph
* Explore vertices and edges using a worklist algorithm
* Compute a subproblem result for each vertex
* Postprocess to extract a final result
* Idea behind *dynamic programming* design:
* Frame the problem as a problem on a directed acyclic graph (DAG)
* Explore vertices in topological order
* Compute a subproblem result for each vertex
* Postprocess to extract a final result
* Idea behind *greedy* design:
* Frame the problem as a problem on a directed acyclic graph (DAG)
* Start at a source vertex
* Compute and follow a "best" outgoing edge at each vertex (without exploring anything else)
* Postprocess to extract a final result
--------------------------------------------------------------------------------
# Problem 1:
A vehicle can travel up to a distance of 10 without refueling, and along its route, there are nine locations, `a` through `i`, where it can be refueled; these locations are spaced as follows:
* The distance from `a` to `b` is 5.
* The distance from `b` to `c` is 3.
* The distance from `c` to `d` is 3.
* The distance from `d` to `e` is 8.
* The distance from `e` to `f` is 3.
* The distance from `f` to `g` is 2.
* The distance from `g` to `h` is 2.
* The distance from `h` to `i` is 4.
If the vehicle is to start empty at `a` and end up full at `i`, where should it refuel to minimize the number of refuelings?
Example: The vehicle could:
* Fuel at `a`,
* Travel 5 to fuel at `b`,
* Travel 6 to fuel at `d`,
* Travel 8 to fuel at `e`,
* Travel 3 to fuel at `f`,
* Travel 4 to fuel at `h`, and
* Travel 4 for fuel at `i`.
This plan takes seven refuelings, but there are plans with fewer.
--------------------------------------------------------------------------------
# Dynamic Programming for the Fueling Problem
Problem: Given a sequence of fueling sites, the distances between consecutive sites, and a maximum range per fueling, find a plan for traveling over the sequence with as few refuelings as possible.
## DAG
* Vertices (situations):
* Locations
* Edges (actions):
* Ways to advance to a location up to `range` units of distance away
* Edge weights (optional):
* Distance to the next location
* Topological order:
* Order along the route: `a → b → … → i`
* Goal:
* Find the path from `a` to `i` with the fewest edges
## Backpointer Class
* Information for the final result:
* The previous location
* Information to go back:
* Nothing additional (since we already have the previous location)
* Information to compare quality:
* The number of fuelings so far
## Choosing a Backpointer
* Exhaustive search:
* Generate all preceding locations out to a distance of `range`
* Check that the backpointer minimizes the number of fuelings so far
## Example
* The range per fueling is 10.
* The distance from `a` to `b` is 5.
* The distance from `b` to `c` is 3.
* The distance from `c` to `d` is 3.
* The distance from `d` to `e` is 8.
* The distance from `e` to `f` is 3.
* The distance from `f` to `g` is 2.
* The distance from `g` to `h` is 2.
* The distance from `h` to `i` is 4.
Location Previous Location Fuelings so Far
-------- ----------------- ---------------
a ⊥ 1
b a 2
c a 2
d c 3
e d 4
f e 5
g e 5
h e 5
i h 6
Reversed Path
----
i → h → e → d → c → a
--------------------------------------------------------------------------------
# Greedy Algorithm for the Fueling Problem
Problem: [same as above]
## DAG
* [same as above]
## Choosing a Forward Edge
* Exhaustive search:
* Generate …
* Check that …
## Example
* The range per fueling is 10.
* The distance from `a` to `b` is 5.
* The distance from `b` to `c` is 3.
* The distance from `c` to `d` is 3.
* The distance from `d` to `e` is 8.
* The distance from `e` to `f` is 3.
* The distance from `f` to `g` is 2.
* The distance from `g` to `h` is 2.
* The distance from `h` to `i` is 4.
Location Next Location
-------- -------------
a …
Path
----
a → …
--------------------------------------------------------------------------------
# Problem 2:
You are creating a lossless compression scheme for text that uses the symbols 'a' through 'd' at the following rates:
* 'a' occurs about 15% of the time in a typical text.
* 'b' occurs about 30% of the time in a typical text.
* 'c' occurs about 25% of the time in a typical text.
* 'd' occurs about 30% of the time in a typical text.
The compressed text will be saved in a binary format, so your compression scheme needs to encode each symbol into a sequence of zeros and ones. For it to be lossless, there can be no ambiguities in the encoding. For example, the encoding
* 'a' → `0`
* 'b' → `1`
* 'c' → `00`
* 'd' → `11`
must be lossy because binary like `0011` could decode to any of 'aabb', 'aad', 'cbb', or 'cd'.
What lossless compression scheme minimizes the expected number of bits per symbol?
Example: The lossless encoding
* 'a' → `0`
* 'b' → `10`
* 'c' → `110`
* 'd' → `1110`
has `15% * 1 + 30% * 2 + 25% * 3 + 30% * 4 = 2.7` expected bits per symbol. But there is an encoding with better compression.
--------------------------------------------------------------------------------
# Guaranteeing a Lossless Encoding
For a lossless code that can be decoded from left to right without lookahead, the code words for two distinct symbols always take this form:
* `[common prefix]0[first suffix]`
* `[common prefix]1[second suffix]`
(Proof: The only other possiblity is that one code word is a prefix of the other. But after reading the prefix, a no-lookahead decoder would not know whether or not to keep reading bits, which contracts the losslessness of the encoding.)
So if we already have suffixes for two symbols `x` and `y`, we just need to find a common prefix meaning "`x` or `y`", and then we will have the symbols' full code words.
## Example
Symbol Partitioning 'a' 'b' 'c' 'd'
-------------------------- --- --- --- ---
{'a'}, {'b'}, {'c'}, {'d'}
{'a', 'b'}, {'c'}, {'d'} 0 1
{'a', 'b', 'c'}, {'d'} 00 01 1
{'a', 'b', 'c', 'd'} 000 001 01 1
There's still the question of how to choose which sets to combine in each step. But one clue is that each combination costs a bit for each symbol in the combined set.
--------------------------------------------------------------------------------
# Greedy Algorithm for the Encoding Problem
Problem: Given frequencies for a set of symbols, create a lossless binary encoding that minimizes the expected number of bits per symbols.
## DAG
* Vertices (situations):
*
* Edges (actions):
*
* Edge weights (optional):
*
* Topological order:
*
* Goal:
*
## Choosing a Forward Edge
* Direct solution:
*
*
*
## Example
* 'a' occurs about 15% of the time in a typical text.
* 'b' occurs about 30% of the time in a typical text.
* 'c' occurs about 25% of the time in a typical text.
* 'd' occurs about 30% of the time in a typical text.
Symbol Partitioning (Frequency Order) 'a' 'b' 'c' 'd'
------------------------------------------------------- --- --- --- ---
{'a'} → 0.15, {'c'} → 0.25, {'b'} → 0.30, {'d'} → 0.30
This diff is collapsed.
{
"name": "@unlsoft/greedy-algorithms",
"version": "1.0.0",
"description": "Starter code for the class on greedy algorithms.",
"private": true,
"license": "UNLICENSED",
"scripts": {
"lint:css": "stylelint \"**/*.css\" \"**/*.module.css\" \"!coverage/**\"",
"lint:js": "eslint --max-warnings 0 ./src",
"lint": "run-s --continue-on-error lint:**",
"test-once": "react-scripts test --watchAll=false --coverage",
"test": "react-scripts test --watchAll --coverage",
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
},
"homepage": ".",
"dependencies": {
"@reduxjs/toolkit": "^1.6.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.1.9",
"classnames": "^2.3.1",
"npm-run-all": "^4.1.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.3",
"workbox-background-sync": "^5.1.3",
"workbox-broadcast-update": "^5.1.3",
"workbox-cacheable-response": "^5.1.3",
"workbox-core": "^5.1.3",
"workbox-expiration": "^5.1.3",
"workbox-google-analytics": "^5.1.3",
"workbox-navigation-preload": "^5.1.3",
"workbox-precaching": "^5.1.3",
"workbox-range-requests": "^5.1.3",
"workbox-routing": "^5.1.3",
"workbox-strategies": "^5.1.3",
"workbox-streams": "^5.1.3"
},
"devDependencies": {
"@unlsoft/eslint-config": "file:../eslint-config",
"@unlsoft/stylelint-config": "file:../stylelint-config",
"eslint-plugin-jest-dom": "^3.9.0",
"stylelint": "^13.13.1"
},
"stylelint": {
"extends": "@unlsoft/stylelint-config"
},
"eslintConfig": {
"extends": [
"react-app",
"@unlsoft/eslint-config/react"
]
},
"jest": {
"clearMocks": true,
"collectCoverageFrom": [
"src/features/**/*.js"
],
"resetMocks": false,
"restoreMocks": false
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<meta
name="description"
content="Starter code for the class on greedy algorithms."
/>
<meta name="theme-color" content="rgba(208 0 0 / 100%)" />
<link rel="icon" href="%PUBLIC_URL%/logo.svg" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo.svg" />
<title>Greedy Algorithms in Class</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 152 152">
<rect x="0" y="0" width="152" height="152" fill="rgba(0 0 0 / 100%)" />
<path d="M147,1H90V42h10V75.673L53.532,2.393,52.648,1H2V42H12v66H2v41H62V108H52V74.336l46.467,73.271L99.351,149H150V108H140V42h10V1Z" stroke-width="3" stroke="rgba(255 255 255 / 100%)" fill="rgba(208 0 0 / 100%)">
</path>
</svg>
{
"short_name": "Greedy Algorithms",
"name": "Greedy Algorithms in Class",
"description": "Starter code for the class on greedy algorithms.",
"icons": [
{
"src": "logo.svg",
"type": "image/svg+xml",
"sizes": "192x192 512x512",
"purpose": "any maskable"
}
],
"start_url": ".",
"display": "standalone",
"orientation": "portrait",
"theme_color": "rgba(208 0 0 / 100%)",
"background_color": "rgba(255 255 255 / 100%)"
}
import { Route } from 'react-router-dom';
import { Solution as FuelingSolution } from './features/fueling/solution.js';
import { Solution as EncodingSolution } from './features/encoding/solution.js';
export function App() {
return (
<>
<Route exact path={'/'}>
<h1>Fueling Problem</h1>
<FuelingSolution />
<h1>Encoding Problem</h1>
<EncodingSolution />
</Route>
</>
);
}
export class PriorityQueue {
constructor() {
this._vertices = [];
}
get size() {
return this._vertices.length;
}
insert(element, measure) {
let index = this._vertices.length;
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2);
const parent = this._vertices[parentIndex];
if (parent.measure < measure) {
break;
}
this._vertices[index] = parent;
index = parentIndex;
}
this._vertices[index] = {
element,
measure,
};
}
remove() {
console.assert(this._vertices.length > 0, 'Cannot remove an element from an empty priority queue');
const resultVertex = this._vertices[0];
const result = [resultVertex.element, resultVertex.measure];
const vertex = this._vertices[this._vertices.length - 1];
for (let index = 0; ;) {
this._vertices[index] = vertex;
let swapIndex = index;
for (const candidateIndex of [2 * index + 1, 2 * index + 2]) {
if (candidateIndex < this._vertices.length - 1 &&
this._vertices[candidateIndex].measure < this._vertices[swapIndex].measure) {
swapIndex = candidateIndex;
}
}
if (swapIndex === index) {
this._vertices[index] = vertex;
this._vertices.pop();
return result;
}
this._vertices[index] = this._vertices[swapIndex];
index = swapIndex;
}
}
}
This diff is collapsed.
/* eslint-disable no-magic-numbers */
import { findEncoding } from './solver.js';
import encodingImage from './images/encoding.svg';
import styles from './solution.module.css';
function formatMap(solution) {
const contents = [...solution.entries()].map(([meaning, code]) => `${meaning}${code}`).join(', ');
return `{${contents}}`;
}
function score(frequencies, encoding) {
let result = 0;
for (const [meaning, bits] of encoding) {
result += frequencies.get(meaning) * bits.length;
}
return result;
}
const FIRST_PROBLEM_FREQUENCIES = new Map([
['a', 0.15],
['b', 0.30],
['c', 0.25],
['d', 0.30],
]);
const SECOND_PROBLEM_FREQUENCIES = new Map([
['a', 0.15],
['b', 0.40],
['c', 0.25],
['d', 0.20],
]);
export function Solution() {
const firstSolution = findEncoding(FIRST_PROBLEM_FREQUENCIES);
const secondSolution = findEncoding(SECOND_PROBLEM_FREQUENCIES);
return (
<div>
<figure className={styles.diagram} >
<img src={encodingImage} alt="A weighted directed graph corresponding to the encoding problem." />
</figure>
<label>
Frequencies: {formatMap(FIRST_PROBLEM_FREQUENCIES)}<br/>
Chosen Encoding:
<output>
{formatMap(firstSolution)}<br/>
({score(FIRST_PROBLEM_FREQUENCIES, firstSolution).toFixed(3)} bit[s] per symbol)
</output>
</label><br/><br/>
<label>
Frequencies: {formatMap(SECOND_PROBLEM_FREQUENCIES)}<br/>
Chosen Encoding:
<output>
{formatMap(secondSolution)}<br/>
({score(SECOND_PROBLEM_FREQUENCIES, secondSolution).toFixed(3)} bit[s] per symbol)
</output>
</label>
</div>
);
}
.diagram {
display: block;
box-sizing: border-box;
margin: auto;
width: 100%;
overflow-x: scroll;
}
export function findEncoding(frequencies) {
const results = new Map();
for (const [meaning, frequency] of frequencies) {
results.set(meaning, '');
}
// TODO: stub
return results;
}
This diff is collapsed.
/* eslint-disable no-magic-numbers */
import { planFuelings } from './solver.js';
import fuelingImage from './images/fueling.svg';
import styles from './solution.module.css';
function formatSolution(solution) {
return solution.map((index) => String.fromCodePoint('a'.codePointAt(0) + index)).join(', ');
}