Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -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' );
}

} )();
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
6 changes: 6 additions & 0 deletions records/change.csv
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions records/due-paid.csv
Original file line number Diff line number Diff line change
@@ -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
124 changes: 124 additions & 0 deletions shared/ChangeBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
Author: Greg Goodhile
*/

'use strict';

/*
Parse data as a list of records and evaluate the change owed back to customer.
data <String>
Returns <Array>
*/
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 <String>
// Returns <Number> | <NaN>
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;
};

/*
Create a comma-separated change denomination phrase
cents <Number>
Returns <String>
*/
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 };
38 changes: 38 additions & 0 deletions shared/FileSys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Author: Greg Goodhile
*/

'use strict';

const fs = require( 'fs' );

/*
Read input file
path <String>
Returns <String>
*/
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 <String>
Returns <String>
*/
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 };