Cash Register Challenge

Challenge:

Design a cash register program

Specifications:

Your register currently has the following bills/coins within it:

'PENNY': .01,

'NICKEL': .05,

'DIME': .10,

'QUARTER': .25,

'HALF DOLLAR': .50,

'ONE': 1.00,

'TWO': 2.00,

'FIVE': 5.00,

'TEN': 10.00,

'TWENTY': 20.00,

'FIFTY': 50.00,

'ONE HUNDRED': 100.00

Your program should accept as its first argument a path to a filename. The input file contains several lines. Each line is one test case. Each line contains two numbers which are separated by a semicolon. The first is the Purchase price (PP) and the second is the cash(CH).

For each set of input produce a single line of output, which is the cashBack to be returned to the customer. In case the CH < PP, print out ERROR. If CH == PP, print out ZERO. For all other cases print the amount that needs to be returned, in terms of the currency values provided. The output should be sorted in highest-to-lowest order

Solution:

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class CashRegister {
public static void main(String[] args) throws FileNotFoundException {
File file = new File(args[0]);
Scanner fileInput = new Scanner(file);

while (fileInput.hasNextLine()) {
printCashChange(fileInput.nextLine());
}
}

private static void printCashChange(String line) {
float pp = Float.parseFloat(line.split(";")[0]);
float ch = Float.parseFloat(line.split(";")[1]);

System.out.println(getCashChange(pp, ch));
}

private static String getCashChange(float price, float cash) {
if (cash < price) { return "ERROR"; }
if (cash == price) { return "ZERO"; }
float cashBack = cash - price;

StringBuilder change = new StringBuilder();

while (cashBack > 0.01f) {
if (cashBack >= 100.0f) {
change.append("ONE HUNDRED");
cashBack -= 100.0f;
} else if (cashBack >= 50.0f) {
change.append("FIFTY");
cashBack -= 50.0f;
} else if (cashBack >= 20.0f) {
change.append("TWENTY");
cashBack -= 20.0f;
} else if (cashBack >= 10.0f) {
change.append("TEN");
cashBack -= 10.0f;
} else if (cashBack >= 5.0f) {
change.append("FIVE");
cashBack -= 5.0f;
} else if (cashBack >= 2.0f) {
change.append("TWO");
cashBack -= 2.0f;
} else if (cashBack >= 1.0f) {
change.append("ONE");
cashBack -= 1.0f;
} else if (cashBack >= 0.5f) {
change.append("HALF DOLLAR");
cashBack -= 0.5f;
} else if (cashBack >= 0.25f) {
change.append("QUARTER");
cashBack -= 0.25f;
} else if (cashBack >= 0.1f) {
change.append("DIME");
cashBack -= 0.1f;
} else if (cashBack >= 0.05f) {
change.append("NICKEL");
cashBack -= 0.05f;
} else {
change.append("PENNY");
cashBack -= 0.01f;
}
change.append(",");
}
change.setLength(change.length() - 1);

return change.toString();
}
}


Every now and then one creates a solution that works but really comes off as sub-optimal, this is one of those times for this CRer. It really seems verbose, I'd appreciate any tips on improving performance and general structure.

For convenience, even though the challenge doesn't specify it, I suggest optionally taking input from System.in. It's more reasonable behaviour than crashing with an ArrayIndexOutOfBoundsException.

Scanner input = (args.length > 0) ? new Scanner(new File(args[0]))
: new Scanner(System.in);


In printCashChange(), avoid two calls to split().

String[] fields = line.split(";", 2);
float pp = Float.parseFloat(fields[0]);
float ch = Float.parseFloat(fields[1]);


The loop in getCashChange() should be data-driven, like this:

for (Denomination d : Denomination.values()) {
while (cashBack >= d.getValue()) {
cashBack -= d.getValue();
change.append(d).append(',');
}
}


Here's an enum to support that:

public enum Denomination {
ONE_HUNDRED(100.00f),
FIFTY( 50.00f),
TWENTY( 20.00f),
TEN( 10.00f),
FIVE(  5.00f),
TWO(  2.00f),
ONE(  1.00f),
HALF_DOLLAR(  0.50f),
QUARTER(  0.25f),
DIME(  0.10f),
NICKEL(  0.05f),
PENNY(  0.01f);

private final float value;
private final String description;

Denomination(float value) {
this.value = value;
this.description = this.name().replace("_", " ");
}

public float getValue() {
return this.value;
}

@Override
public String toString() {
return this.description;
}
}


The performance of your program will be fine. You aren't doing anything particularly inefficient, and the bottleneck will be I/O rather than processing.

• This part is clever: for (Denomination d : Denomination.values()) { while (cashBack >= d.getValue()) { ... }. One more thing, how do you ensure that always the bigger value will get substracted first? Commented Jan 20, 2015 at 6:37
• Commented Jan 20, 2015 at 6:39
• I would really like to know how you approach the code. For e.g. I would never have come up with something like this - Denomination(float value) { ... this.description = this.name().replace("_", " "); ...} and then using it as public String toString() { return this.description; } and finally as change.append(d).append(',');. If you have a git repo for the code, please post it here too. Commented Jan 20, 2015 at 19:01