diff --git a/README.md b/README.md
index 11da620..f420633 100755
--- a/README.md
+++ b/README.md
@@ -3,3 +3,8 @@ Welcome to the 2nd Mock Machine Coding Round by [workat.tech](http://workat.tech
Please visit [this link](https://workat.tech/machine-coding/practice/splitwise-problem-0kp2yneec2q2) to participate.
+Compile and package:
+mvn clean package
+
+Run
+java -jar target/splitwise-1.0-SNAPSHOT.jar
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c81f999
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+ mc-prep
+ com.sameerpandit
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ splitwise
+
+ 11
+ 11
+ com.sameerpandit.Main
+
+
+
+
+ org.glassfish.hk2
+ hk2-locator
+ 3.0.2
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
+ ${main}
+
+
+
+
+
+
+
+ org.glassfish.hk2
+ hk2-inhabitant-generator
+ 3.0.2
+
+
+
+ generate-inhabitants
+
+
+
+
+
+
+
diff --git a/src/main/java/com/sameerpandit/Driver.java b/src/main/java/com/sameerpandit/Driver.java
new file mode 100644
index 0000000..4397f74
--- /dev/null
+++ b/src/main/java/com/sameerpandit/Driver.java
@@ -0,0 +1,76 @@
+package com.sameerpandit;
+
+import com.sameerpandit.db.InMemRepository;
+
+import org.jvnet.hk2.annotations.Service;
+
+import jakarta.inject.Inject;
+
+@Service
+public class Driver {
+ @Inject
+ Splitwise splitwise;
+ @Inject
+ InMemRepository repository;
+ public void run(){
+ System.out.println("Running Driver");
+ test1();
+ repository.clear();
+ test2();
+ }
+
+ public void test1(){
+ System.out.println("------------------------------------------------------");
+ String user1 = splitwise.addUser("User1", "user1@gmail.com", "9986368688");
+ String user2 = splitwise.addUser("User2", "user2@gmail.com", "9986368689");
+ String user3 = splitwise.addUser("User3", "user3@gmail.com", "9986368682");
+ String user4 = splitwise.addUser("User4", "user4@gmail.com", "9986368683");
+ splitwise.show();
+// System.out.println("------------------------------------------------------");
+ splitwise.show(user1);
+// System.out.println("------------------------------------------------------");
+ //EXPENSE u1 1000 4 u1 u2 u3 u4 EQUAL
+ splitwise.addExpense(1000,"EQUAL",user1, new String[]{user1, user2, user3,user4});
+ splitwise.show(user4);
+// System.out.println("------------------------------------------------------");
+ splitwise.show(user1);
+// System.out.println("------------------------------------------------------");
+ //EXPENSE u1 1250 2 u2 u3 EXACT 370 880
+ splitwise.addExpense(1250,"EXACT",user1, new String[]{user1, user2, user3}, new Integer[]{0, 370, 880});
+ splitwise.show();
+// System.out.println("------------------------------------------------------");
+ //EXPENSE u4 1200 4 u1 u2 u3 u4 PERCENT 40 20 20 20
+ splitwise.addExpense(1200,"PERCENT",user4, new String[]{user1, user2, user3, user4}, new Integer[]{40, 20, 20, 20});
+ splitwise.show(user1);
+// System.out.println("------------------------------------------------------");
+ splitwise.show();
+// System.out.println("------------------------------------------------------");
+ }
+
+ public void test2(){
+ System.out.println("------------------------------------------------------");
+ String user1 = splitwise.addUser("User1", "user1@gmail.com", "9986368688");
+ String user2 = splitwise.addUser("User2", "user2@gmail.com", "9986368689");
+ String user3 = splitwise.addUser("User3", "user3@gmail.com", "9986368682");
+ splitwise.show();
+ // System.out.println("------------------------------------------------------");
+ splitwise.show(user1);
+ // System.out.println("------------------------------------------------------");
+ //EXPENSE u1 1000 4 u1 u2 u3 u4 EQUAL
+ splitwise.addExpense(1000,"EQUAL",user1, new String[]{user1, user2, user3});
+ splitwise.show(user3);
+ // System.out.println("------------------------------------------------------");
+ splitwise.show(user1);
+ // System.out.println("------------------------------------------------------");
+ //EXPENSE u1 1250 2 u2 u3 EXACT 370 880
+ splitwise.addExpense(1250,"EXACT",user1, new String[]{user1, user2,user3 }, new Integer[]{0, 370, 880});
+ splitwise.show();
+ // System.out.println("------------------------------------------------------");
+ //EXPENSE u4 1200 4 u1 u2 u3 u4 PERCENT 40 20 20 20
+ splitwise.addExpense(1200,"PERCENT",user3, new String[]{user1, user2, user3}, new Integer[]{60, 20, 20});
+ splitwise.show(user1);
+ // System.out.println("------------------------------------------------------");
+ splitwise.show();
+ // System.out.println("------------------------------------------------------");
+ }
+}
diff --git a/src/main/java/com/sameerpandit/Main.java b/src/main/java/com/sameerpandit/Main.java
new file mode 100644
index 0000000..4a79e08
--- /dev/null
+++ b/src/main/java/com/sameerpandit/Main.java
@@ -0,0 +1,12 @@
+package com.sameerpandit;
+
+import org.glassfish.hk2.api.ServiceLocator;
+import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
+
+public class Main {
+ public static void main(String[] args){
+ ServiceLocator locator = ServiceLocatorUtilities.createAndPopulateServiceLocator();
+ Driver driver = locator.getService(Driver.class);
+ driver.run();
+ }
+}
diff --git a/src/main/java/com/sameerpandit/Splitwise.java b/src/main/java/com/sameerpandit/Splitwise.java
new file mode 100644
index 0000000..06b47dc
--- /dev/null
+++ b/src/main/java/com/sameerpandit/Splitwise.java
@@ -0,0 +1,38 @@
+package com.sameerpandit;
+
+import com.sameerpandit.service.ExpenseManagerService;
+import com.sameerpandit.service.UserService;
+
+import org.jvnet.hk2.annotations.Service;
+
+import jakarta.inject.Inject;
+
+@Service
+public class Splitwise {
+
+ @Inject
+ ExpenseManagerService expenseManagerService;
+
+ @Inject
+ UserService userService;
+
+ public String addUser(String name, String email, String phone) {
+ return userService.addUser(name,email,phone);
+ }
+
+ public void addExpense(Integer amount, String type, String initiatorId, String[] participants){
+ expenseManagerService.addExpense(amount,type,initiatorId,participants);
+ }
+
+ public void addExpense(Integer amount, String type, String initiatorId, String[] participants, Integer[] splits){
+ expenseManagerService.addExpense(amount,type,initiatorId,participants,splits);
+ }
+
+ public void show(){
+ expenseManagerService.show();
+ }
+
+ public void show(String userId){
+ expenseManagerService.show(userId);
+ }
+}
diff --git a/src/main/java/com/sameerpandit/business/Constants.java b/src/main/java/com/sameerpandit/business/Constants.java
new file mode 100644
index 0000000..aea3ce0
--- /dev/null
+++ b/src/main/java/com/sameerpandit/business/Constants.java
@@ -0,0 +1,6 @@
+package com.sameerpandit.business;
+
+public class Constants {
+ public static Integer PERCENT_SCALE = 100;
+ public static Integer EXPENSE_SCALING_FACTOR = 100;
+}
diff --git a/src/main/java/com/sameerpandit/business/EqualSplitStrategy.java b/src/main/java/com/sameerpandit/business/EqualSplitStrategy.java
new file mode 100644
index 0000000..2069e42
--- /dev/null
+++ b/src/main/java/com/sameerpandit/business/EqualSplitStrategy.java
@@ -0,0 +1,33 @@
+package com.sameerpandit.business;
+
+import com.sameerpandit.models.Expense;
+
+import org.jvnet.hk2.annotations.Service;
+
+import jakarta.inject.Named;
+
+@Service
+@Named("EQUAL")
+public class EqualSplitStrategy implements SplitStrategy{
+ @Override
+ public Integer[] split(Expense expense) {
+ int amountUnScaled = expense.getUnscaledAmount();
+ int totalParts = (expense.getParticipants().length);
+ boolean cleanSplit = (amountUnScaled%totalParts == 0);
+ Integer[] splitAmounts = new Integer[totalParts];
+ int start = 0;
+ if(!cleanSplit){
+ while(amountUnScaled!=0){
+ int part = amountUnScaled/(totalParts-start);
+ splitAmounts[start] = part;
+ amountUnScaled-=part;
+ start++;
+ }
+ }else{
+ for(;start> creditorMap = new ConcurrentHashMap<>();
+ private ConcurrentHashMap> debitorMap = new ConcurrentHashMap<>();
+ private ConcurrentHashMap userMap = new ConcurrentHashMap<>();
+ private ConcurrentHashMap> expenseInitiatorMap = new ConcurrentHashMap<>();
+ private AtomicInteger userIdNumber = new AtomicInteger(0);
+
+ public void clear(){
+ creditorMap = new ConcurrentHashMap<>();
+ debitorMap = new ConcurrentHashMap<>();
+ userMap = new ConcurrentHashMap<>();
+ expenseInitiatorMap = new ConcurrentHashMap<>();
+ userIdNumber = new AtomicInteger(0);
+ }
+
+ public List getCreditTransactionsByUserId(String userId) {
+ return creditorMap.getOrDefault(userId, new LinkedList<>());
+ }
+
+ public List getDebitTransactionsByUserId(String userId) {
+ return debitorMap.getOrDefault(userId, new LinkedList<>());
+ }
+
+ public List getExpensesInitiatedByUserId(String userId) {
+ return expenseInitiatorMap.get(userId);
+ }
+
+ public User getUserDetailsById(String userId) {
+ return userMap.get(userId);
+ }
+
+ public List getUsers(){
+ LinkedList users = new LinkedList<>();
+ users.addAll(userMap.values());
+ return users;
+ }
+
+ public String addUser(String name, String email, String phone) {
+ String userId = "u" + userIdNumber.incrementAndGet();
+ userMap.put(userId, new User(userId, name, email, phone));
+ return userId;
+ }
+
+ public Expense addExpense(Integer amount, String type, String initiatorId, String[] participants) {
+ Expense expense = new Expense(amount, type, initiatorId, participants);
+ expenseInitiatorMap.computeIfAbsent(initiatorId, k -> new LinkedList<>()).add(expense);
+ return expense;
+ }
+
+ public Expense addExpense(Integer amount, String type, String initiatorId, String[] participants, Integer[] splits) {
+ Expense expense = new Expense(amount, type, initiatorId, participants,splits);
+ expenseInitiatorMap.computeIfAbsent(initiatorId, k -> new LinkedList<>()).add(expense);
+ return expense;
+ }
+
+
+ public void addCreditorTransaction(String creditorId, Transaction tx) {
+ creditorMap.computeIfAbsent(creditorId, k -> new LinkedList<>()).add(tx);
+ }
+
+ public void addDebitorTransaction(String debitorId, Transaction tx) {
+ debitorMap.computeIfAbsent(debitorId, k -> new LinkedList<>()).add(tx);
+ }
+
+}
diff --git a/src/main/java/com/sameerpandit/models/Expense.java b/src/main/java/com/sameerpandit/models/Expense.java
new file mode 100644
index 0000000..5a12cb8
--- /dev/null
+++ b/src/main/java/com/sameerpandit/models/Expense.java
@@ -0,0 +1,64 @@
+package com.sameerpandit.models;
+
+import com.sameerpandit.business.Constants;
+
+public class Expense {
+ private Integer unscaledAmount;
+ private String type;
+ private String initiatorId;
+ private String[] participants;
+ private Integer[] unscaledSplitAmounts;
+ private Integer[] splitPercentages;
+
+
+ public Expense(Integer amount, String type, String initiatorId, String[] participants) {
+ this.unscaledAmount = amount*Constants.EXPENSE_SCALING_FACTOR;
+ this.type = type;
+ this.initiatorId = initiatorId;
+ this.participants = participants;
+ }
+
+ public Integer[] getUnscaledSplitAmounts() {
+ return unscaledSplitAmounts;
+ }
+
+ public Integer[] getSplitPercentages() {
+ return splitPercentages;
+ }
+
+ public Expense(Integer amount, String type, String initiatorId, String[] participants, Integer[] splits) {
+ this.unscaledAmount = amount*Constants.EXPENSE_SCALING_FACTOR;
+ this.type = type;
+ this.initiatorId = initiatorId;
+ this.participants = participants;
+ if("EXACT".equals(type)){
+ unscaledSplitAmounts = splits;
+ for(int i=0;i splitStrategies;
+
+ private SplitStrategy getSplitStrategy(String expenceTye){
+ return splitStrategies.named(expenceTye).get();
+ }
+
+ public void addExpense(Integer amount, String type, String initiatorId, String[] participants){
+ Expense e = repository.addExpense(amount, type, initiatorId, participants);
+ ledgerService.addTransaction(e,getSplitStrategy(e.getType()));
+ }
+
+ public void addExpense(Integer amount, String type, String initiatorId, String[] participants, Integer[] splits){
+ Expense e = repository.addExpense(amount, type, initiatorId, participants,splits);
+ ledgerService.addTransaction(e,getSplitStrategy(e.getType()));
+ }
+
+ public void show(){
+ ledgerService.show();
+ }
+
+ public void show(String userId){
+ ledgerService.show(userId);
+ }
+
+}
diff --git a/src/main/java/com/sameerpandit/service/LedgerService.java b/src/main/java/com/sameerpandit/service/LedgerService.java
new file mode 100644
index 0000000..8b26567
--- /dev/null
+++ b/src/main/java/com/sameerpandit/service/LedgerService.java
@@ -0,0 +1,113 @@
+package com.sameerpandit.service;
+
+import com.sameerpandit.business.Constants;
+import com.sameerpandit.business.SplitStrategy;
+import com.sameerpandit.db.InMemRepository;
+import com.sameerpandit.models.Expense;
+import com.sameerpandit.models.NoBalanceException;
+import com.sameerpandit.models.Transaction;
+import com.sameerpandit.models.User;
+
+import org.jvnet.hk2.annotations.Service;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+
+import jakarta.inject.Inject;
+
+@Service
+public class LedgerService {
+ @Inject
+ InMemRepository repository;
+
+ public void addTransaction(Expense e, SplitStrategy splitStrategy){
+ Integer[] splitAmounts = splitStrategy.split(e);
+ int start = 0;
+ for(String participant: e.getParticipants()){
+ Transaction tx = new Transaction(splitAmounts[start],e.getInitiatorId(), participant);
+ repository.addCreditorTransaction(e.getInitiatorId(),tx);
+ repository.addDebitorTransaction(participant,tx);
+ start++;
+ }
+ }
+
+ public void show(){
+ HashSet> userPairs = new HashSet<>();
+ try{
+ for(Map.Entry> entry : getUserBalancesForUsers().entrySet()){
+ String userId = entry.getKey();
+ for(Map.Entry uEntry: entry.getValue().entrySet()){
+ TreeSet pair = new TreeSet<>();
+ pair.add(userId);pair.add(uEntry.getKey());
+ User u1 = repository.getUserDetailsById(userId);
+ User u2 = repository.getUserDetailsById(uEntry.getKey());
+ if(userId.equals(u2.getUserId()) || userPairs.contains(pair))
+ continue;
+ logExpense(u1.getName(),u2.getName(),uEntry.getValue());
+ userPairs.add(pair);
+ }
+ }
+ }catch (NoBalanceException e){
+ System.out.println("No Balances");
+ }
+
+ }
+
+ public void logExpense(String debitor, String creditor, Integer unscaledAmount){
+ if(unscaledAmount<0)
+ if(unscaledAmount%Constants.EXPENSE_SCALING_FACTOR==0)
+ System.out.println(debitor + " owes " + creditor + " :" + (-1 * unscaledAmount / Constants.EXPENSE_SCALING_FACTOR));
+ else
+ System.out.println(debitor + " owes " + creditor + " :" + ((float)(-1 * unscaledAmount) / Constants.EXPENSE_SCALING_FACTOR));
+ else
+ if(unscaledAmount%Constants.EXPENSE_SCALING_FACTOR==0)
+ System.out.println(creditor + " owes " + debitor + " :" + (unscaledAmount / Constants.EXPENSE_SCALING_FACTOR));
+ else
+ System.out.println(creditor + " owes " + debitor + " :" + ((float)unscaledAmount/ Constants.EXPENSE_SCALING_FACTOR));
+ }
+
+ public void show(String userId){
+ try{
+ User user = repository.getUserDetailsById(userId);
+ for(Map.Entry entry: getUserBalancesForUser(userId).entrySet()){
+ User otherUser = repository.getUserDetailsById(entry.getKey());
+ if(userId.equals(otherUser.getUserId()))
+ continue;
+ logExpense(user.getName(),otherUser.getName(),entry.getValue());
+ }
+ }catch (NoBalanceException e){
+ System.out.println("No Balances");
+ }
+ }
+
+ private Map getUserBalancesForUser(String userId) throws NoBalanceException {
+ List creditorTxs = repository.getCreditTransactionsByUserId(userId);
+ List debitorTxs = repository.getDebitTransactionsByUserId(userId);
+ Map balances = new HashMap<>();
+ for(Transaction tx: creditorTxs){
+ balances.put(tx.getDebitorId(), balances.getOrDefault(tx.getDebitorId(),0)+tx.getUnscaledAmount());
+ }
+ for(Transaction tx: debitorTxs){
+ balances.put(tx.getCreditorId(), balances.getOrDefault(tx.getCreditorId(),0)-tx.getUnscaledAmount());
+ }
+ if(balances.isEmpty())
+ throw new NoBalanceException();
+ return balances;
+ }
+
+ private Map> getUserBalancesForUsers() throws NoBalanceException {
+ List users = repository.getUsers();
+ Map> userBalances = new HashMap<>();
+ for(User user: users){
+ String userId = user.getUserId();
+ Map balance = getUserBalancesForUser(userId);
+ userBalances.put(userId,balance);
+ }
+ if(userBalances.isEmpty())
+ throw new NoBalanceException();
+ return userBalances;
+ }
+}
diff --git a/src/main/java/com/sameerpandit/service/UserService.java b/src/main/java/com/sameerpandit/service/UserService.java
new file mode 100644
index 0000000..d01cdf3
--- /dev/null
+++ b/src/main/java/com/sameerpandit/service/UserService.java
@@ -0,0 +1,28 @@
+package com.sameerpandit.service;
+
+import com.sameerpandit.db.InMemRepository;
+import com.sameerpandit.models.User;
+
+import org.jvnet.hk2.annotations.Service;
+
+import java.util.List;
+
+import jakarta.inject.Inject;
+
+@Service
+public class UserService {
+ @Inject
+ InMemRepository repository;
+
+ public String addUser(String name, String email, String phone) {
+ return repository.addUser(name,email,phone);
+ }
+
+ public User getUserDetails(String userId) {
+ return repository.getUserDetailsById(userId);
+ }
+
+ public List getUsers(){
+ return repository.getUsers();
+ }
+}