Skip to content

Instantly share code, notes, and snippets.

@kerrishotts
Last active July 1, 2019 19:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kerrishotts/461a9057d3f40966e953dde3e441639b to your computer and use it in GitHub Desktop.
Save kerrishotts/461a9057d3f40966e953dde3e441639b to your computer and use it in GitHub Desktop.
Dev.to Daily Challenge #4 (July 1, 2019) (See: https://dev.to/thepracticaldev/daily-challenge-4-checkbook-balancing-hei)
const sanitize = str => str.replace(/[^0-9A-Za-z\.\s]/g, "");
const notBlank = str => str !== "";
const extract = str => {
const [ line, category, expense ] = str.split(/\s+/);
return { line: Number(line), category, expense: Number(expense) };
};
const byLineAndCategory = (a, b) => a.line < b.line
? -1 : a.line > b.line
? 1 : a.category < b.category
? -1 : a.category > b.category
? 1 : 0;
const balanceReducer = (
{openingBalance, totalExpenses, entries},
{line, category, expense}
) => {
const newTotal = totalExpenses + expense;
const newBalance = openingBalance - newTotal;
return {
openingBalance,
totalExpenses: newTotal,
averageExpense: newTotal / (entries.length + 1),
entries: [ ...entries, {line, category, expense, balance: newBalance }]
}
};
const round2 = n => (Math.round(n * 100) / 100)
.toLocaleString(undefined, {
style: "decimal",
minimumFractionDigits: 2,
useGrouping: true
});
const balanceCheckbook = (checkbook) => {
const [openingBalanceStr, ...entries] =
sanitize(checkbook)
.trim()
.split("\n")
.filter(notBlank);
const openingBalance = Number(openingBalanceStr);
const initialState = {
openingBalance,
entries: [],
averageExpense: 0,
totalExpenses: 0
};
const report =
entries
.map(extract)
.sort(byLineAndCategory)
.reduce( balanceReducer, initialState );
return `
Original Balance: ${round2(report.openingBalance)}
${report.entries.map(({line, category, expense, balance}) =>
`${line} ${category} ${round2(expense)} Balance ${round2(balance)}`
).join("\n")}
Total Expenses: ${round2(report.totalExpenses)}
Average Expense: ${round2(report.averageExpense)}
`.trim();
};
const tests = () => {
const assert = (fn, expect) => {
try {
const r = fn();
if (r !== expect) throw new Error(`Expected\n"${expect}"\nGot\n"${r}"`);
} catch(err) {
throw new Error(`${err.message}`);
}
};
// check average
assert( () => balanceCheckbook(`
100.00
100 EntryOne 50.00
101 EntryTwo 50.00
`), `Original Balance: 100.00
100 EntryOne 50.00 Balance 50.00
101 EntryTwo 50.00 Balance 0.00
Total Expenses: 100.00
Average Expense: 50.00`
);
// blank lines?
assert( () => balanceCheckbook(`
100.00
100 EntryOne 50.00
101 EntryTwo 50.00
`), `Original Balance: 100.00
100 EntryOne 50.00 Balance 50.00
101 EntryTwo 50.00 Balance 0.00
Total Expenses: 100.00
Average Expense: 50.00`
);
// Random characters?
assert( () => balanceCheckbook(`
100.00
100 EntryOne#() 50.00
101 EntryTwo;_ 50.00
`), `Original Balance: 100.00
100 EntryOne 50.00 Balance 50.00
101 EntryTwo 50.00 Balance 0.00
Total Expenses: 100.00
Average Expense: 50.00`
);
// what if there are no entries?
assert( () => balanceCheckbook(`
100.00
`), `Original Balance: 100.00
Total Expenses: 0.00
Average Expense: 0.00`
);
// negative?
assert( () => balanceCheckbook(`
1000.00
100 Groceries 79.99
103 Transmission 4,999.39
`), `Original Balance: 1,000.00
100 Groceries 79.99 Balance 920.01
103 Transmission 4,999.39 Balance -4,079.38
Total Expenses: 5,079.38
Average Expense: 2,539.69`
);
}
tests();
// example checkbook
const checkbook = `
1233.00
125 Hardware;! 24.8?;
123 Flowers 93.5
127 Meat 120.90
120 Picture 34.00
124 Gasoline 11.00
123 Photos;! 71.4?;
122 Picture 93.5
132 Tires;! 19.00,?;
129 Stamps 13.6
129 Fruits{} 17.6
129 Market;! 128.00?;
121 Gasoline;! 13.6?;
`;
console.log(balanceCheckbook(checkbook));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment