We're a place where coders share, stay up-to-date and grow their careers.
// UTILS const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); const map = (mapping: { (x: any, index: number): string; (value: any, index: number, array: any[]): {}; }) => (xs: any[]) => xs.map(mapping); const sum = (values: number[]) => values.reduce((total, current) => total + current, 0); const toLines = (text: string) => text.split("\n"); const sort = (sortFunction: (a: any, b: any) => number) => (xs: any[]) => xs.sort(sortFunction); // DOMAIN interface Checkbook { initialBalance: number; checks: Check[]; } interface Check { id: string; amount: number; type: string; } const total = (checks: Check[]) => sum(checks.map(toAmount)); const average = (checks: Check[]) => total(checks) / checks.length; const toAmount = ({ amount }) => amount; const currentBalance = (initialBalance: number, checks: Check[]) => initialBalance - total(checks); const byId = { asc: (a: Check, b: Check) => Number(a.id) - Number(b.id), desc: (a: Check, b: Check) => Number(b.id) - Number(a.id), }; // PARSING const parseId = (text: string) => text; const parseType = (text: string) => text.replace(/[^a-zA-Z0-9_]/, "").replace(/[!}]/, ""); const parseAmount = (text: string) => parseFloat(text); const parseCheck = (text: string): Check => { const [id, type, amount] = text.split(" "); return { id: parseId(id), type: parseType(type), amount: parseAmount(amount), }; }; const parse = (text: string) => { const [initialBalance, ...checks] = toLines(text); return { initialBalance: parseAmount(initialBalance), checks: checks.map(parseCheck), }; }; // HELPERS const displayAmount = (amount: number) => amount.toFixed(2); const col = (...children: string[]) => children.join("\n"); // const row = (...children: string[]) => children.join(" "); // COMPONENTS const InitialBalance = ({ balance }: { balance: number }) => `Original_Balance: ${displayAmount(balance)}`; const Check = ({ check }: { check: Check }) => `${check.id} ${check.type} ${displayAmount(check.amount)}`; const RunningBalance = ({ balance }: { balance: number }) => `Balance ${displayAmount(balance)}`; const CheckRow = ({ check, balance }) => `${Check({ check })} ${RunningBalance({ balance })}`; const TotalExpense = ({ total }: { total: number }) => `Total expense ${displayAmount(total)}`; const AverageExpense = ({ average }: { average: number }) => `Average expense ${displayAmount(average)}`; // APP const Checkbook = ({ checkbook: { initialBalance, checks }, }: { checkbook: Checkbook; }) => { const checkRows: (checks: Check[]) => string[] = pipe( sort(byId.asc), map((check: Check, index: number) => CheckRow({ check, balance: currentBalance(initialBalance, checks.slice(0, index + 1)), // index + 1 means inclusive }), ), ); return col( InitialBalance({ balance: initialBalance }), ...checkRows(checks), TotalExpense({ total: total(checks) }), AverageExpense({ average: average(checks) }), ); }; // WRAPPER export const checkbookBalancer = (text: string) => { const checkbook = parse(text); return Checkbook({ checkbook }); };
Discussion on: Daily Challenge #4 - Checkbook Balancing