0

I got a little project that I had to complete for a job. I am sure you guys have seen this project on here. I am want two things, tip on how to get the correct answer and also a peer review on my solution so far.

public class OrderMethod {

    static Map<String, BigDecimal> newOrder;

    static final BigDecimal BASICSALES = new BigDecimal(.10);
    static final BigDecimal IMPORTSALES = new BigDecimal(.05);
    static final BigDecimal BASICANDIMPORTSALES = new BigDecimal(.15);
    static final BigDecimal Rounding = new BigDecimal(0.05);
    static BigDecimal total = new BigDecimal(0);

    public void convertOrders() throws IOException {

        ReadFile readfile = new ReadFile();
        ArrayList<String> order = readfile.getFile();
        newOrder = new LinkedHashMap<String, BigDecimal>();

        for(String i : order) {
            String[] splitOrder = i.split("at\\ ");
            newOrder.put(splitOrder[0], new BigDecimal(splitOrder[1]));
        }
    }

    public void calculate() {
        newOrder.forEach((k, v) -> {

            if(!((k.contains("chocolate")) || k.contains("book") || k.contains("pill")) && k.contains("import")){
                v = v.multiply(BASICANDIMPORTSALES).add(v);
                v = v.round(new MathContext(4));

                System.out.println("Both " + k + " tax: $" + v);
                newOrder.put(k, v);
            }
            else {
                if(!(k.contains("chocolate")|| k.contains("book") || k.contains("pill"))) {

                    v = v.multiply(BASICSALES).add(v);
                    v = v.round(new MathContext(4));

                    System.out.println("Basic " + k + " tax: $" + v);
                newOrder.put(k, v);
            }
            if(k.contains("import")) {
                v = v.multiply(IMPORTSALES).add(v);
                v = v.round(new MathContext(4));

                System.out.println("Import " + k + " tax: $" + v);

                newOrder.put(k, v);
            }
            }

            total = total.add(v);

        });
    }

    public void print() {
        newOrder.forEach((k, v) -> {
            System.out.println(k + ": $" + v);
        });
        System.out.println("Total: $" + total);
    }

    public static void main(String[] args) throws IOException {
        OrderMethod om = new OrderMethod();
        om.convertOrders();

        om.calculate();
        om.print();
    }
}

So what I have done is that I have the program reading a text file containing inputs such as

Input 1:
1 book at 12.49
1 music CD at 14.99
1 chocolate bar at 0.85

Input 2:
1 imported box of chocolates at 10.00
1 imported bottle of perfume at 47.50

Input 3:
1 imported bottle of perfume at 27.99
1 bottle of perfume at 18.99
1 packet of headache pills at 9.75
1 box of imported chocolates at 11.25

These are the list of solutions: Output:

Output 1:
1 book : 12.49
1 music CD: 16.49
1 chocolate bar: 0.85
Sales Taxes: 1.50
Total: 29.83

Output 2:
1 imported box of chocolates: 10.50
1 imported bottle of perfume: 54.65
Sales Taxes: 7.65
Total: 65.15

Output 3:
1 imported bottle of perfume: 32.19
1 bottle of perfume: 20.89
1 packet of headache pills: 9.75
1 imported box of chocolates: 11.85
Sales Taxes: 6.70
Total: 74.68

I am having a little problem with my calculation. No matter what my answers to Input 2 and 3 seems to be a couple of decimal places off. I get $65.12 for Input 2 and $74.64 for Input 3. I want help on how to better round up my answers and also what do you think of my code so far.

khelwood
  • 55,782
  • 14
  • 81
  • 108

2 Answers2

0

I don't understand your code but I had to do this on my exam.

I use the operator % like this and it works.

if((number * 100)%10 >= 5)

0

To round up to 5 cents, you can these :

private static final BigDecimal TWENTY = new BigDecimal(20);

public BigDecimal roundUp5(BigDecimal value)
{
    // The final setScale(2) is just to add the ".00" when you display
    return value.multiply(TWENTY).setScale(0, BigDecimal.ROUND_CEILING).divide(TWENTY).setScale(2);
}

// You don't need this one as you're using BigDecimal;  it's just for completeness!
public double roundUp5(double value)
{
    return Math.ceil(value * 20) / 20;
}

Happy to provide my thoughts as a code review.

Firstly, why the separate convertOrders and calculate methods ? Creating the Map in convertOrders, and then iterating across it and re-inserting in calculate is messy and confusing.

Much better would be to have convertOrders call calculate as part of its iteration as each line is read, and therefore create each Map entry correctly as it goes.

You have some repeated expressions that I would refactor into methods for readability and prospect of future business changes, so :

public boolean isBasic(String itemName) {
    String name = itemName.toLowerCase();
    return !( name.contains("chocolate") || name.contains("book") || name.contains("pill") );
}


public boolean isImport(String itemName) {
    String name = itemName.toLowerCase();
    return name.contains("import");
}

In your if statements in calculate currently run as :

if (isBasic(k) && isImport(k)) {
    // Basic and Import
} else {
   if (isBasic(k)) {
   }
   if (isImport(k)) {
   }
}

That flow is a bit confusing, and would be a pain to refactor if in future you needed to also handle non-basic and non-imported items.

There's some common code between all these if blocks, so I would move that out to after the if - which would leave the log statement and the setting of the multiplication factor the only lines remaining in the if blocks.

While we're talking about the multiplication factors, you have those set to values less than 1 (eg 0.15) ... bit then you add v to the result ! It would be much better to have the factors be > 1 and so get rid of the add(v).

Incidentally, it is also preferable to initialise BigDecimal to Strings rather than literal float or double, since those can get rounding errors (eg, 0.15 as a float might actually be 0.1499999999) whereas BigDecimal created from a String is exact.

Assuming you're happy to leave the logging of the final amount to the end, that gets us to something like :

static final BigDecimal BASICSALES = new BigDecimal("1.10");
static final BigDecimal IMPORTSALES = new BigDecimal("1.05");
static final BigDecimal BASICANDIMPORTSALES = new BigDecimal("1.15");


   for(String orderLine : order) {
        String[] splitOrder = orderLine.split("at\\ ");
        String name = splitOrder[0];
        BigDecimal value = new BigDecimal(splitOrder[1]));

       BigDecimal taxRate;

       if(isBasic(name)) {
           if (isImport(name)) {
               taxRate = BASICANDIMPORTSALES;
               System.out.println("Both " + k);
           } else {
               taxRate = BASICSALES;
               System.out.println("Basic " + k);
           }
        } else {
           if (isImport(name)) {
               taxRate = IMPORTSALES;
               System.out.println("Import " + k);
           } else {
               taxRate = BigDecimal.ONE;
               System.out.println("Neither " + k);
           }
        }

        BigDecimal amountWithTax = amount.multiply(taxRate).round(new MathContext(4));
        System.out.println(name + " tax: $" + amountWithTax);
        newOrder.put(name, amountWithTax);
   }
racraman
  • 4,988
  • 1
  • 16
  • 16
  • Thank you very much for your advise. I will see to it to improve my code. I am new to programming and I understand that my code is ugly but I definitely want to get better. I like how you didn't tear me a new one but also gave me good feedback and example for me to think over. If you can, can you provide tips or resources on how to write cleaner code over time? Thank You. – ACompleteNoobSmoke Feb 01 '20 at 21:42
  • Glad to help :) Personal tips would be : 1) Follow SOLID and DRY and YAGNI principles. 2) Variable names - single letters only for iteration controllers (like `i`, etc), otherwise it is never a mistake to use words describing what it *IS* rather than how it is used (eg, from above, `v` is because it is used as the value of the map, but it *IS* actually the amount, which is why I renamed it). 3) keep boolean variables and expressions "positive" - eg `if (isBasic)` rather than `if (! isBook)`. – racraman Feb 03 '20 at 01:18
  • The aim is to have someone reading the code to be able to scan down it almost like reading a story; It should flow intuitively from top to bottom, without the user having to consciously figure out what's going on. Every cryptic name, break in logic, or `!` "not" expression, etc gives the story a "jarring" effect that makes the reader bring the mental-heavy-machinery to analyze it. – racraman Feb 03 '20 at 01:24
  • Book recommendation : "Clean Code" by Robert C. Martin. Enjoy :) – racraman Feb 03 '20 at 01:27