constsanitize=str=>str.replace(/[^0-9A-Za-z\.\s]/g,"");constnotBlank=str=>str!=="";constextract=str=>{const[line,category,expense]=str.split(/\s+/);return{line:Number(line),category,expense:Number(expense)};};constbyLineAndCategory=(a,b)=>a.line<b.line?-1:a.line>b.line?1:a.category<b.category?-1:a.category>b.category?1:0;constbalanceReducer=({openingBalance,totalExpenses,entries},{line,category,expense})=>{constnewTotal=totalExpenses+expense;constnewBalance=openingBalance-newTotal;return{openingBalance,totalExpenses:newTotal,averageExpense:newTotal/(entries.length+1),entries:[...entries,{line,category,expense,balance:newBalance}]}};constround2=n=>(Math.round(n*100)/100).toLocaleString(undefined,{style:"decimal",minimumFractionDigits:2,useGrouping:true});constbalanceCheckbook=(checkbook)=>{const[openingBalanceStr,...entries]=sanitize(checkbook).trim().split("\n").filter(notBlank);constopeningBalance=Number(openingBalanceStr);constinitialState={openingBalance,entries:[],averageExpense:0,totalExpenses:0};constreport=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();};
Passionate about solving problems since 2012! I'm a full stack web developer with experience in AWS, TypeScript and React. Looking for new opportunities!
Here's my take (JavaScript). A few notes:
I think is the most scalable solution since you first move all the data to a manipulable format, deal with it and then output it.