From 207d57e6bb12c669d27442d18fba8f10e10b376f Mon Sep 17 00:00:00 2001 From: Greg Goodhile Date: Mon, 23 Mar 2020 00:42:45 -0400 Subject: [PATCH 1/2] Program complete --- index.js | 56 ++++++++++++++++++ package.json | 19 +++++++ records/change.csv | 6 ++ records/due-paid.csv | 6 ++ shared/ChangeBuilder.js | 122 ++++++++++++++++++++++++++++++++++++++++ shared/FileSys.js | 38 +++++++++++++ 6 files changed, 247 insertions(+) create mode 100644 index.js create mode 100644 package.json create mode 100644 records/change.csv create mode 100644 records/due-paid.csv create mode 100644 shared/ChangeBuilder.js create mode 100644 shared/FileSys.js diff --git a/index.js b/index.js new file mode 100644 index 00000000..152f30ea --- /dev/null +++ b/index.js @@ -0,0 +1,56 @@ +/* +Creative Cash Draw Solutions - Cash Register (demo program for SoftWriters) +https://github.com/SoftWriters/CashRegister + +CLIENT GOALS / REQUIREMENTS +Please write a program which accomplishes the clients goals. The program should: + * Accept a flat file as input + * Each line will contain the total due and the amount paid separated by a comma (for example: 2.13,3.00) + * Expect that there will be multiple lines + * Output the change the cashier should return to the customer + * The return string should look like: 1 dollar,2 quarters,1 nickel, etc ... + * Each new line in the input file should be a new line in the output file + + +Author: Greg Goodhile + +TECHNOLOGIES + Node + JavaScript + +TO RUN + node index.js -OR- npm run start + +*/ + +'use strict'; + +( () => { + + const { readFile, writeFile } = require( './shared/FileSys' ); + const { parseRecords, makeChangePhrase } = require( './shared/ChangeBuilder' ); + + const inputDataPath = './records/due-paid.csv'; + const outDataPath = './records/change.csv'; + + const inputData = readFile( inputDataPath ); + + if ( inputData ) { + try { + // parse records from input file as amount due and amount paid; values converted to cents + const inputRecords = parseRecords( inputData ); + // create output records in the form of comma-separated change denomination phrases + const outputRecords = inputRecords.map( makeChangePhrase ); + // convert records array to a string with a newline character after each record + const outData = outputRecords.join( '\n' ); + + writeFile( outDataPath, outData ); + console.log( 'Output file written' ); + } catch ( err ) { + console.log( err.message ); + } + } else { + console.log( 'No data to retrieve' ); + } + +} )(); diff --git a/package.json b/package.json new file mode 100644 index 00000000..de2c77ae --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "cashregister", + "version": "1.0.0", + "description": "Cash register service that will parse a flat file, perform logic to get the value of change owed to customer, convert value to change denominations equaling that value, write a new file to disk with that value.", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/solarsnail/CashRegister.git" + }, + "author": "Greg Goodhile", + "license": "ISC", + "bugs": { + "url": "https://github.com/solarsnail/CashRegister/issues" + }, + "homepage": "https://github.com/solarsnail/CashRegister#readme" +} \ No newline at end of file diff --git a/records/change.csv b/records/change.csv new file mode 100644 index 00000000..44b87f0a --- /dev/null +++ b/records/change.csv @@ -0,0 +1,6 @@ +1 quarter,1 nickel,2 pennies +6 dollars,1 quarter,1 nickel,1 penny +136 dimes,8 pennies +1 dollar,1 penny +1 dollar +10 dollars,1 quarter,1 dime,2 pennies \ No newline at end of file diff --git a/records/due-paid.csv b/records/due-paid.csv new file mode 100644 index 00000000..7688df09 --- /dev/null +++ b/records/due-paid.csv @@ -0,0 +1,6 @@ +4.68,5.00 +3.69,10.00 +6.32,20.00 +8.99,10.00 +4.01,5.01 +9.63,20.00 \ No newline at end of file diff --git a/shared/ChangeBuilder.js b/shared/ChangeBuilder.js new file mode 100644 index 00000000..9ffc09e9 --- /dev/null +++ b/shared/ChangeBuilder.js @@ -0,0 +1,122 @@ +/* +Author: Greg Goodhile +*/ + +'use strict'; + +/* +Parse data as a list of records and evaluate the change owed back to customer. +data +Returns +*/ +const parseRecords = ( data ) => { + // Convert "number" string into cents/pennies; this removes a decimal point from the string; no need to worry about math precision using float numbers. + // Removing decimal from "number" string emulates multiplying a number by 100 to get cents/pennies. + // v + // Returns | + const toCents = ( v ) => { + // length must be at least 3 which signifies a valid currency decimal, ex: .45 + // if a decimal is not found two places from the right, v is not an expected currency value + const vlen = v.length; + if ( vlen < 3 || v.indexOf( '.' ) !== vlen - 3 ) { + return NaN; + } + + return Number( v.replace( '.', '' ) ); + }; + + let recordList = []; + let dataLine = 1; + for ( const record of data.split( /\r?\n/ ) ) { + const recordSet = record.split( ',' ); + if ( recordSet.length !== 2 ) { + throw new Error( `Invalid record set at line ${dataLine} : missing two values separated by a comma.` ); + } + + const due = toCents( recordSet[ 0 ] ); + const paid = toCents( recordSet[ 1 ] ); + + if ( Number.isNaN( due ) || Number.isNaN( paid ) ) { + throw new Error( `Invalid record set at line ${dataLine} : invalid float value found while parsing.` ); + } + + if ( paid < due ) { + throw new Error( `Invalid record set at line ${dataLine} : amount paid is less than amount due.` ); + } + // calculate the change owed + recordList.push( paid - due ); + dataLine++; + } + + return recordList; +}; + +/* + +*/ +const makeChangePhrase = ( cents ) => { + // Mapping for change quantity phrases + const phraseMap = { + noun100: ( qty ) => { + if ( qty === 0 ) return ''; + return qty === 1 ? '1 dollar,' : `${qty} dollars,`; + }, + noun25: ( qty ) => { + if ( qty === 0 ) return ''; + return qty === 1 ? '1 quarter,' : `${qty} quarters,`; + }, + noun10: ( qty ) => { + if ( qty === 0 ) return ''; + return qty === 1 ? '1 dime,' : `${qty} dimes,`; + }, + noun5: ( qty ) => { + if ( qty === 0 ) return ''; + return qty === 1 ? '1 nickel,' : `${qty} nickels,`; + }, + noun1: ( qty ) => { + if ( qty === 0 ) return ''; + return qty === 1 ? '1 penny,' : `${qty} pennies,`; + } + }; + + // get cent count at centSize and leftOver cents + const denominationMap = ( leftOver, centSize ) => { + return { + centSize, + leftOver: leftOver % centSize, + count: Math.floor( leftOver / centSize ) + }; + }; + + // order of change denominations + let denomOrderList = [ 100, 25, 10, 5, 1 ]; + + // "DIVISIBLE BY 3 ~ TWIST CHECK" + if ( cents % 3 === 0 ) { + // shuffle denomOrderList + let tmpOrder = []; + for ( let i = denomOrderList.length - 1; i >= 0; i-- ) { + const randIndex = Math.floor( Math.random() * ( i + .99 ) ); + tmpOrder.push( denomOrderList[ randIndex ] ); + denomOrderList.splice( randIndex, 1 ); + } + denomOrderList = tmpOrder; + } + + // list of change mappings + let changeList = denomOrderList.map( ( v ) => { + let tmpCounter = denominationMap( cents, v ); + cents = tmpCounter.leftOver; + return tmpCounter; + } ); + + let phrase = phraseMap.noun100( changeList.find( v => v.centSize === 100 ).count ); + phrase += phraseMap.noun25( changeList.find( v => v.centSize === 25 ).count ); + phrase += phraseMap.noun10( changeList.find( v => v.centSize === 10 ).count ); + phrase += phraseMap.noun5( changeList.find( v => v.centSize === 5 ).count ); + phrase += phraseMap.noun1( changeList.find( v => v.centSize === 1 ).count ); + + return phrase.slice( 0, -1 ); +}; + +module.exports = { parseRecords, makeChangePhrase }; \ No newline at end of file diff --git a/shared/FileSys.js b/shared/FileSys.js new file mode 100644 index 00000000..cae573cb --- /dev/null +++ b/shared/FileSys.js @@ -0,0 +1,38 @@ +/* +Author: Greg Goodhile +*/ + +'use strict'; + +const fs = require( 'fs' ); + +/* +Read input file +path +Returns +*/ +const readFile = ( path ) => { + try { + const data = fs.readFileSync( path, 'utf8' ); + return data.length > 0 ? data : null; + } catch ( err ) { + console.log( err.message ); + return null; + } +}; + +/* +write output file +path +Returns +*/ +const writeFile = ( path, data ) => { + try { + fs.writeFileSync( path, data, 'utf8' ); + } catch ( err ) { + console.log( err.message ); + throw new Error( 'Cannot write output file' ); + } +}; + +module.exports = { readFile, writeFile }; \ No newline at end of file From d4c0f8dbce16aff4b96352edcb8d58c43f0f154a Mon Sep 17 00:00:00 2001 From: Greg Goodhile Date: Mon, 23 Mar 2020 00:51:08 -0400 Subject: [PATCH 2/2] updated comments --- shared/ChangeBuilder.js | 10 ++++++---- shared/FileSys.js | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/shared/ChangeBuilder.js b/shared/ChangeBuilder.js index 9ffc09e9..cbddacf5 100644 --- a/shared/ChangeBuilder.js +++ b/shared/ChangeBuilder.js @@ -6,13 +6,13 @@ Author: Greg Goodhile /* Parse data as a list of records and evaluate the change owed back to customer. -data -Returns +data +Returns */ const parseRecords = ( data ) => { // Convert "number" string into cents/pennies; this removes a decimal point from the string; no need to worry about math precision using float numbers. // Removing decimal from "number" string emulates multiplying a number by 100 to get cents/pennies. - // v + // v // Returns | const toCents = ( v ) => { // length must be at least 3 which signifies a valid currency decimal, ex: .45 @@ -52,7 +52,9 @@ const parseRecords = ( data ) => { }; /* - + Create a comma-separated change denomination phrase + cents + Returns */ const makeChangePhrase = ( cents ) => { // Mapping for change quantity phrases diff --git a/shared/FileSys.js b/shared/FileSys.js index cae573cb..901723f5 100644 --- a/shared/FileSys.js +++ b/shared/FileSys.js @@ -8,8 +8,8 @@ const fs = require( 'fs' ); /* Read input file -path -Returns +path +Returns */ const readFile = ( path ) => { try { @@ -23,8 +23,8 @@ const readFile = ( path ) => { /* write output file -path -Returns +path +Returns */ const writeFile = ( path, data ) => { try {