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

Initial commit.

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 604 additions and 0 deletions
# 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
Please clone the starter code linked from Canvas (with `git clone --recursive` so that you also clone the submodules), install its dependencies with `npm install`, and use "File" → "Open Workspace…" to open your clone in VSCode. Please do not run the app yet—it contains spoilers.
--------------------------------------------------------------------------------
# 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
* Edges (actions):
* Ways to advance to a location up to `range` units of distance away
* Vertices (situations):
* Locations
* 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
* Edges (actions):
*
* Vertices (situations):
*
* Edge weights:
*
* 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(', ');
}
const GAPS = [5, 3, 3, 8, 3, 2, 2, 4];
const RANGE = 10;
export function Solution() {
const solution = planFuelings(GAPS, RANGE);
return (
<div>
<figure className={styles.diagram} >
<img src={fuelingImage} alt="A weighted directed graph corresponding to the fueling problem." />
</figure>
<label>
Chosen Fueling Points: <output>{formatSolution(solution)}</output>
</label>
</div>
);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment