From: Scott Worley Date: Thu, 23 May 2019 08:43:11 +0000 (-0700) Subject: Start X-Git-Tag: v1.0~22 X-Git-Url: http://git.scottworley.com/inverse-tax/commitdiff_plain/0993d859b81ccc99c56b5ded5ed85067dfe8196f?ds=sidebyside Start --- 0993d859b81ccc99c56b5ded5ed85067dfe8196f diff --git a/tax.html b/tax.html new file mode 100644 index 0000000..3b0ab91 --- /dev/null +++ b/tax.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tax.js b/tax.js new file mode 100644 index 0000000..2b5bd4a --- /dev/null +++ b/tax.js @@ -0,0 +1,49 @@ +"use strict"; + +function parse_table(as_text) { + function parse_line(line) { + return line.trim().split(' ').filter(x => x !== '').map(x => parseFloat(x)); + } + return as_text.trim().split('\n').map(parse_line); +} + +function parse_tax_table(as_text) { + return parse_table(as_text).map(([start, rate], i, table) => + [start, i < table.length - 1 ? table[i+1][0] : Infinity, rate / 100.0]); +} + +function sum(nums) { + return nums.reduce((total, num) => total + num, 0); +} + +function tax(table, income) { + return sum(table.map(([start, end, rate]) => + Math.max(0, Math.min(income, end) - start) * rate)); +} + +function apply_deductible(table, deductible) { + return table.map(([start, end, rate]) => [start + deductible, end + deductible, rate]); +} + +function merge_tax_tables(t1, t2) { + if (t1.length == 0) return t2; + if (t2.length == 0) return t1; + [start1, end1, rate1] = t1[0]; + [start2, end2, rate2] = t2[0]; + if (start1 == start2) { + if (end1 == end2) { + return [[start1, end1, rate1 + rate2]].concat(merge_tax_tables(t1.slice(1), t2.slice(1))); + } + if (end1 < end2) { + return [[start1, end1, rate1 + rate2]].concat(merge_tax_tables(t1.slice(1), [[end1, end2, rate2]].concat(t2.slice(1)))); + } + return merge_tax_tables(t2, t1); + } + if (start1 < start2) { + if (end1 <= start2) { + return [t1[0]].concat(merge_tax_tables(t1.slice(1), t2)); + } + return [[start1, start2, rate1]].concat(merge_tax_tables([[start2, end1, rate1]].concat(t1.slice(1)), t2)); + } + return merge_tax_tables(t2, t1); +} diff --git a/tax.test.js b/tax.test.js new file mode 100644 index 0000000..192eac0 --- /dev/null +++ b/tax.test.js @@ -0,0 +1,29 @@ +"use strict"; + +var assert = require('assert'); + +function test(description, f) { + f(); +} + +function near(a, b, epsilon = 1e-6) { + return Math.abs(a - b) < epsilon; +} + +test("parse tax table", () => { + const parsed = parse_tax_table(' 1 2\n10 4\n'); + assert.strictEqual(parsed.length, 2); + assert.deepStrictEqual(parsed[0], [1, 10, .02]); + assert.deepStrictEqual(parsed[1], [10, Infinity, .04]); +}); + +test("sum", () => { + assert.strictEqual(sum([]), 0); + assert.strictEqual(sum([7]), 7); + assert.strictEqual(sum([100, 1, 10]), 111); +}); + +test("tax", () => { + assert.strictEqual(tax([[10, 100, .01], [100, Infinity, .1]], 150), 5.9); + assert.ok(near(tax([[10, 100, .01], [100, Infinity, .1]], 150), 5.9)); +}); diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..b595d60 --- /dev/null +++ b/test.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +test_js= +cleanup() { + if [[ "$test_js" && -e "$test_js" ]];then + rm -rf "$test_js" + fi +} +trap cleanup EXIT +test_js=$(mktemp) + +cat *.js > "$test_js" + +node "$test_js"