Skip to content

Commit

Permalink
Add test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
LimZiJia committed Mar 25, 2024
1 parent d631ac1 commit 501bcfb
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 32 deletions.
3 changes: 3 additions & 0 deletions data/transactions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a 40.10
b 40.10
c 10.00
10 changes: 6 additions & 4 deletions src/main/java/balancer/logic/command/AddCommand.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package balancer.logic.command;

import java.io.IOException;
import java.text.DecimalFormat;

import balancer.storage.Storage;

Expand All @@ -12,24 +13,25 @@ public class AddCommand extends Command {
public static final String COMMAND_WORD = "add";
private static final String ADD_COMMAND_SUCCESS = "'s transaction has been successfully added!";
private final String name;
private final int amount;
private final double amount;

/**
* Constructs an {@code AddCommand} with the specified name and amount.
*
* @param name the name of the individual involved in the transaction.
* @param amount the amount of the transaction.
*/
public AddCommand(String name, int amount) {
public AddCommand(String name, double amount) {
this.name = name;
this.amount = amount;
}

@Override
public CommandResult execute(Storage storage) throws IOException {
DecimalFormat df = new DecimalFormat("0.00");
storage.addTransaction(name, amount);
storage.save();
return new CommandResult(String.format("%s%s %s has now contributed $%.2f",
name, ADD_COMMAND_SUCCESS, name, storage.getAmount(name)));
return new CommandResult(String.format("%s%s %s has now contributed $%s",
name, ADD_COMMAND_SUCCESS, name, df.format(storage.getAmount(name))));
}
}
37 changes: 21 additions & 16 deletions src/main/java/balancer/logic/command/CalculateCommand.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package balancer.logic.command;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

import balancer.storage.Storage;
import balancer.storage.Transaction;
Expand All @@ -14,14 +17,15 @@
*/
public class CalculateCommand extends Command {
public static final String COMMAND_WORD = "calculate";
public static final String NO_TRANSACTIONS_REPLY = "All good! No transactions required!";

@Override
public CommandResult execute(Storage storage) {
HashMap<String, Transaction> transactions = storage.getTransactions();

// Remove all people that contributed exactly the average amount and transforms the amounts to their difference
// from the average. +ve means then are owed money and -ve means that they owe money.
ArrayList<Transaction> processed = preprocess(transactions);
List<Transaction> processed = preprocess(transactions);

// Run the greedy algorithm to get the minimum number of transactions
String result = greedy(processed);
Expand All @@ -38,24 +42,19 @@ public CommandResult execute(Storage storage) {
* @param hashmap Taken straight from the storage. Maps names to {@code Transaction}.
* @return An {@code ArrayList<Transaction>} of the processed {@code HashMap}.
*/
private ArrayList<Transaction> preprocess(HashMap<String, Transaction> hashmap) {
ArrayList<Transaction> current = new ArrayList<>(hashmap.values());
float total = 0;
public List<Transaction> preprocess(HashMap<String, Transaction> hashmap) {
List<Transaction> current = new ArrayList<>(hashmap.values());
double total = 0;
for (Transaction t: current) {
total += t.getAmount();
}
float average = total / current.size();
double average = total / current.size();

// Changing transactions to be difference from average
current.replaceAll(x -> x.update(-average));

// Removing all people who have to do nothing (people who are not owed or owe any money)
current.forEach(x -> {
int index = current.indexOf(x);
if (x.isEmpty()) {
current.remove(index);
}
});
current = current.stream().filter(t -> !t.isEmpty()).collect(Collectors.toList());

return current;
}
Expand All @@ -68,9 +67,12 @@ private ArrayList<Transaction> preprocess(HashMap<String, Transaction> hashmap)
* @return The String representation of the list of transactions that need to occur in the form of
* {@code person1 has to pay person2 $X.XX}.
*/
private String greedy(ArrayList<Transaction> processed) {
public String greedy(List<Transaction> processed) {
DecimalFormat df = new DecimalFormat("0.00");
StringBuilder sb = new StringBuilder();
TransactionComparator comparator = new TransactionComparator();
int numberOfTransactions = 0;

while (!processed.isEmpty()) {
// Sort the list
processed.sort(comparator);
Expand All @@ -80,9 +82,10 @@ private String greedy(ArrayList<Transaction> processed) {
Transaction biggestReceiver = processed.get(processed.size() - 1);

// Make the largest possible transaction from biggestGiver to biggestReceiver
float amountToTransfer = Math.min(-biggestGiver.getAmount(), biggestReceiver.getAmount());
sb.append(String.format("%s has to pay %s $%.2f\n",
biggestGiver.getPerson(), biggestReceiver.getPerson(), amountToTransfer));
double amountToTransfer = Math.min(-biggestGiver.getAmount(), biggestReceiver.getAmount());
sb.append(String.format("%s has to pay %s $%s\n",
biggestGiver.getPerson(), biggestReceiver.getPerson(), df.format(amountToTransfer)));
numberOfTransactions++;

// Update transactions and transaction list
biggestGiver.update(amountToTransfer);
Expand All @@ -99,6 +102,8 @@ private String greedy(ArrayList<Transaction> processed) {
}
}

return sb.toString();
return sb.length() == 0
? NO_TRANSACTIONS_REPLY
: sb.append(String.format("\nNumber of transactions: %d", numberOfTransactions)).toString();
}
}
2 changes: 1 addition & 1 deletion src/main/java/balancer/logic/parser/AddParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public AddCommand parse(String userInput) throws ParserException {
}
String[] arguments = userInput.split(" ");
String name = arguments[0];
int amount = Integer.parseInt(arguments[1]);
double amount = Double.parseDouble(arguments[1]);
return new AddCommand(name, amount);
}
}
14 changes: 11 additions & 3 deletions src/main/java/balancer/storage/Storage.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -34,7 +35,7 @@ public Storage(String dir, String name) {
* @param name Name of the person to add or update.
* @param amount The initial or update transaction amount.
*/
public void addTransaction(String name, int amount) {
public void addTransaction(String name, double amount) {
Transaction current = transactions.get(name);
if (current == null) {
current = new Transaction(name, amount);
Expand All @@ -45,10 +46,17 @@ public void addTransaction(String name, int amount) {
}

public HashMap<String, Transaction> getTransactions() {
return this.transactions;
// Deep copy so that this.transactions cannot be altered
HashMap<String, Transaction> copy = new HashMap<>();
for (Map.Entry<String, Transaction> entry : this.transactions.entrySet()) {
String person = entry.getValue().getPerson();
double amount = entry.getValue().getAmount();
copy.put(entry.getKey(), new Transaction(person, amount));
}
return copy;
}

public float getAmount(String name) {
public double getAmount(String name) {
return transactions.get(name).getAmount();
}

Expand Down
17 changes: 10 additions & 7 deletions src/main/java/balancer/storage/Transaction.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package balancer.storage;

import java.text.DecimalFormat;

/**
* Represents a transaction.
*/
public class Transaction {
private String person;
private float amount;
private double amount;

/**
* Constructs a transaction.
*
* @param person Name of the person involved in the transaction.
* @param amount Amount of the transaction.
*/
public Transaction(String person, float amount) {
public Transaction(String person, double amount) {
this.person = person;
this.amount = amount;
}
Expand All @@ -24,7 +26,7 @@ public Transaction(String person, float amount) {
* @param amount Amount to update. Can be negative.
* @return Updated {@code Transaction}.
*/
public Transaction update(float amount) {
public Transaction update(double amount) {
this.amount += amount;
return this;
}
Expand All @@ -33,7 +35,7 @@ public String getPerson() {
return person;
}

public float getAmount() {
public double getAmount() {
return amount;
}

Expand All @@ -43,7 +45,7 @@ public float getAmount() {
* @return Ture if amount is 0.
*/
public boolean isEmpty() {
float epsilon = 0.00001f; // Had to choose an arbitrarily small value due to float equality being bad.
double epsilon = 0.00001; // Had to choose an arbitrarily small value due to double equality being bad.
return Math.abs(amount) < epsilon;
}

Expand All @@ -59,14 +61,15 @@ public static Transaction savedStringToTransaction(String savedString) {
throw new UnsupportedOperationException();
} else {
String newName = split[0];
float newAmount = Float.parseFloat(split[1]);
double newAmount = Double.parseDouble(split[1]);
return new Transaction(newName, newAmount);
}
}

@Override
public String toString() {
return String.format("%s %.2f", person, amount);
DecimalFormat df = new DecimalFormat("0.00");
return String.format("%s %s", person, df.format(amount));
}

}
2 changes: 1 addition & 1 deletion src/main/java/balancer/storage/TransactionComparator.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
public class TransactionComparator implements Comparator<Transaction> {
@Override
public int compare(Transaction t1, Transaction t2) {
return Float.compare(t1.getAmount(), t2.getAmount());
return Double.compare(t1.getAmount(), t2.getAmount());
}
}
118 changes: 118 additions & 0 deletions src/test/java/balancer/logic/command/CalculateCommandTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package balancer.logic.command;

import balancer.storage.Transaction;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class CalculateCommandTest {
CalculateCommand cc = new CalculateCommand();

@Test
public void one_person_pay_two_persons() {
HashMap<String, Transaction> transactions = new HashMap<>();
transactions.put("Alice", new Transaction("Alice", 40));
transactions.put("Bob", new Transaction("Bob", 40));
transactions.put("Charlie", new Transaction("Charlie", 10));
List<Transaction> transactionList = cc.preprocess(transactions);

String expected = "Charlie has to pay Alice $10.00\n" +
"Charlie has to pay Bob $10.00\n" +
"\n" +
"Number of transactions: 2";
String actual = cc.greedy(transactionList);
assertEquals(expected, actual);
}

@Test
public void one_person_pay_two_persons_2dp() {
HashMap<String, Transaction> transactions = new HashMap<>();
transactions.put("Alice", new Transaction("Alice", 40.105));
transactions.put("Bob", new Transaction("Bob", 40.105));
transactions.put("Charlie", new Transaction("Charlie", 10));
List<Transaction> transactionList = cc.preprocess(transactions);

String expected = "Charlie has to pay Alice $10.04\n" +
"Charlie has to pay Bob $10.03\n" +
"\n" +
"Number of transactions: 2";
String actual = cc.greedy(transactionList);
assertEquals(expected, actual);
}

@Test
public void four_persons_1() {
HashMap<String, Transaction> transactions = new HashMap<>();
transactions.put("Alice", new Transaction("Alice", 10));
transactions.put("Bob", new Transaction("Bob", 20));
transactions.put("Charlie", new Transaction("Charlie", 0));
transactions.put("Don", new Transaction("Don", 10));
List<Transaction> transactionList = cc.preprocess(transactions);

String expected = "Charlie has to pay Bob $10.00\n" +
"\n" +
"Number of transactions: 1";
String actual = cc.greedy(transactionList);
assertEquals(expected, actual);
}

@Test
public void four_persons_2() {
HashMap<String, Transaction> transactions = new HashMap<>();
transactions.put("Alice", new Transaction("Alice", 40));
transactions.put("Bob", new Transaction("Bob", 40));
transactions.put("Charlie", new Transaction("Charlie", 10));
transactions.put("Don", new Transaction("Don", 10));
List<Transaction> transactionList = cc.preprocess(transactions);

String expected = "Don has to pay Alice $15.00\n" +
"Charlie has to pay Bob $15.00\n" +
"\n" +
"Number of transactions: 2";
String actual = cc.greedy(transactionList);
assertEquals(expected, actual);
}

@Test
public void four_persons_with_one_significantly_more() {
HashMap<String, Transaction> transactions = new HashMap<>();
transactions.put("Alice", new Transaction("Alice", 200));
transactions.put("Bob", new Transaction("Bob", 80));
transactions.put("Charlie", new Transaction("Charlie", 50));
transactions.put("Don", new Transaction("Don", 20));
List<Transaction> transactionList = cc.preprocess(transactions);

String expected = "Don has to pay Alice $67.50\n" +
"Charlie has to pay Alice $37.50\n" +
"Bob has to pay Alice $7.50\n" +
"\n" +
"Number of transactions: 3";
String actual = cc.greedy(transactionList);
assertEquals(expected, actual);
}

@Test
public void four_persons_with_two_significantly_more() {
HashMap<String, Transaction> transactions = new HashMap<>();
transactions.put("Alice", new Transaction("Alice", 160));
transactions.put("Bob", new Transaction("Bob", 120));
transactions.put("Charlie", new Transaction("Charlie", 50));
transactions.put("Don", new Transaction("Don", 20));
List<Transaction> transactionList = cc.preprocess(transactions);

String expected = "Don has to pay Alice $67.50\n" +
"Charlie has to pay Bob $32.50\n" +
"Charlie has to pay Alice $5.00\n" +
"\n" +
"Number of transactions: 3";
String actual = cc.greedy(transactionList);
assertEquals(expected, actual);
}



}

0 comments on commit 501bcfb

Please sign in to comment.