├── .gitignore ├── README.md ├── golang ├── .idea │ └── .gitignore ├── go.mod ├── internal │ ├── handler │ │ └── handler.go │ ├── model │ │ ├── expense │ │ │ └── expense.go │ │ ├── split │ │ │ ├── equal_split.go │ │ │ ├── exact_split.go │ │ │ ├── percent_split.go │ │ │ └── split.go │ │ └── user.go │ └── service │ │ └── transaction_service.go ├── main.go └── pkg │ └── constants.go └── python ├── .gitignore ├── classes ├── EqualExpense.py ├── ExactExpense.py ├── ExactSplit.py ├── Expense.py ├── Split.py ├── TransactionManager.py ├── User.py └── __init__.py ├── config.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # machine-coding-splitwise 2 | Solved in java and golang. Taken from machine-coding Splitwise workat tech: https://workat.tech/machine-coding/practice/splitwise-problem-0kp2yneec2q2 -------------------------------------------------------------------------------- /golang/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /golang/go.mod: -------------------------------------------------------------------------------- 1 | module machine-coding-splitwise 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /golang/internal/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "machine-coding-splitwise/internal/model" 7 | "machine-coding-splitwise/internal/model/expense" 8 | "machine-coding-splitwise/internal/service" 9 | "machine-coding-splitwise/pkg" 10 | ) 11 | 12 | type TransactionHandler struct { 13 | service service.TransactionService 14 | } 15 | 16 | func GetTransactionHandler() *TransactionHandler { 17 | return &TransactionHandler{ 18 | service: service.GetTransactionService(), 19 | } 20 | } 21 | 22 | func (h *TransactionHandler) AddUser(user *model.User) error { 23 | ok := h.service.AddUser(user) 24 | if !ok { 25 | log.Println("user cannot be added: ", *user) 26 | return errors.New("user cannot be added") 27 | } 28 | return nil 29 | } 30 | 31 | func (h *TransactionHandler) ShowBalance() { 32 | h.service.ShowBalance() 33 | } 34 | 35 | func (h *TransactionHandler) CreateExpense(expenseType pkg.ExpenseType, expense expense.Expense) { 36 | h.service.CreateExpense(expenseType, expense) 37 | } 38 | -------------------------------------------------------------------------------- /golang/internal/model/expense/expense.go: -------------------------------------------------------------------------------- 1 | package expense 2 | 3 | import ( 4 | "machine-coding-splitwise/internal/model" 5 | "machine-coding-splitwise/internal/model/split" 6 | ) 7 | 8 | type Expense struct { 9 | Amount float64 10 | PaidBy *model.User 11 | Splits []*split.Split 12 | } 13 | -------------------------------------------------------------------------------- /golang/internal/model/split/equal_split.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | type EqualSplit struct { 4 | *Split 5 | } 6 | -------------------------------------------------------------------------------- /golang/internal/model/split/exact_split.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | type ExactSplit struct { 4 | *Split 5 | } 6 | -------------------------------------------------------------------------------- /golang/internal/model/split/percent_split.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | type PercentSplit struct { 4 | *Split 5 | } 6 | -------------------------------------------------------------------------------- /golang/internal/model/split/split.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | type Split struct { 4 | UserId string 5 | Amount float64 6 | } 7 | -------------------------------------------------------------------------------- /golang/internal/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type User struct { 4 | Id string 5 | Name string 6 | Email string 7 | Mobile string 8 | } 9 | -------------------------------------------------------------------------------- /golang/internal/service/transaction_service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "log" 5 | "machine-coding-splitwise/internal/model" 6 | "machine-coding-splitwise/internal/model/expense" 7 | "machine-coding-splitwise/pkg" 8 | "math" 9 | "sync" 10 | ) 11 | 12 | type TransactionService interface { 13 | AddUser(user *model.User) bool 14 | ShowBalance() 15 | CreateExpense(expenseType pkg.ExpenseType, expense expense.Expense) 16 | } 17 | 18 | type TransactionServiceInMemoryImpl struct { 19 | BalanceSheet map[string]map[string]float64 // map of user_id to balance_user and amount 20 | } 21 | 22 | var transactionServiceInstance TransactionService 23 | var transactionServiceOnce sync.Once 24 | 25 | func GetTransactionService() TransactionService { 26 | log.Println("init TransactionService") 27 | transactionServiceOnce.Do(func() { 28 | transactionServiceInstance = &TransactionServiceInMemoryImpl{ 29 | BalanceSheet: map[string]map[string]float64{}, 30 | } 31 | }) 32 | return transactionServiceInstance 33 | } 34 | 35 | func (t *TransactionServiceInMemoryImpl) AddUser(user *model.User) bool { 36 | //userId := user.Id 37 | //_, ok := t.BalanceSheet[userId] 38 | //if ok { 39 | // // idempotent call since user already added. 40 | // log.Println("user : is already added", user.Name) 41 | // return false 42 | // //} else { 43 | // // t.BalanceSheet[userId] = map[string]float64{} 44 | // //} 45 | //} 46 | return true 47 | } 48 | 49 | func (t *TransactionServiceInMemoryImpl) ShowBalance() { 50 | if len(t.BalanceSheet) == 0 { 51 | log.Println("no balances") 52 | } else { 53 | for user := range t.BalanceSheet { 54 | t.showBalancePerUser(user) 55 | } 56 | } 57 | } 58 | 59 | func (t *TransactionServiceInMemoryImpl) showBalancePerUser(userId string) { 60 | 61 | for balanceUser, balanceAmount := range t.BalanceSheet[userId] { 62 | if balanceAmount < 0 { 63 | log.Printf("%s is owed by %s: %.2f\n", balanceUser, userId, math.Abs(balanceAmount)) 64 | } else { 65 | log.Printf("%s owes %s: %.2f\n", balanceUser, userId, balanceAmount) 66 | } 67 | } 68 | } 69 | 70 | func (t *TransactionServiceInMemoryImpl) CreateExpense(expenseType pkg.ExpenseType, expense expense.Expense) { 71 | paidByUserId := expense.PaidBy.Id 72 | //t.BalanceSheet[paidByUserId] = map[string]float64{} 73 | if expenseType == pkg.EXPENSE_TYPE_EQUAL { 74 | totalSplits := len(expense.Splits) 75 | splitAmount := math.Round(expense.Amount*100/float64(totalSplits)) / 100.0 76 | for _, split := range expense.Splits { 77 | // ensuring that split.Amount has equal amount for all users in splits slice. 78 | // ideally, should be validated during split creation. 79 | split.Amount = splitAmount 80 | } 81 | } 82 | 83 | for _, split := range expense.Splits { 84 | if paidByUserId != split.UserId { 85 | // add +ve expense beared by paidByUserId for list of users, as provided in split 86 | if paidByUserBalance, ok := t.BalanceSheet[paidByUserId]; ok { 87 | if _, ok := paidByUserBalance[split.UserId]; ok { 88 | t.BalanceSheet[paidByUserId][split.UserId] += split.Amount 89 | } else { 90 | t.BalanceSheet[paidByUserId][split.UserId] = split.Amount 91 | } 92 | } else { 93 | t.BalanceSheet[paidByUserId] = map[string]float64{ 94 | split.UserId: split.Amount, 95 | } 96 | } 97 | // remove from balance sheet if ultimately amount = 0 98 | if val, ok := t.BalanceSheet[paidByUserId][split.UserId]; ok && val == 0 { 99 | delete(t.BalanceSheet[paidByUserId], split.UserId) 100 | } 101 | 102 | // add -ve expense owed by current user for all list of users as provided in split. 103 | // it is -ve expense, since current user owes it to paidByUser 104 | if paidByUserBalance, ok := t.BalanceSheet[split.UserId]; ok { 105 | if _, ok := paidByUserBalance[paidByUserId]; ok { 106 | t.BalanceSheet[split.UserId][paidByUserId] -= split.Amount 107 | } else { 108 | t.BalanceSheet[split.UserId][paidByUserId] = -split.Amount 109 | } 110 | } else { 111 | t.BalanceSheet[split.UserId] = map[string]float64{ 112 | paidByUserId: -split.Amount, 113 | } 114 | } 115 | // remove from balance sheet if ultimately amount = 0 116 | if val, ok := t.BalanceSheet[split.UserId][paidByUserId]; ok && val == 0 { 117 | delete(t.BalanceSheet[split.UserId], paidByUserId) 118 | } 119 | 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "machine-coding-splitwise/internal/handler" 6 | "machine-coding-splitwise/internal/model" 7 | "machine-coding-splitwise/internal/model/expense" 8 | "machine-coding-splitwise/internal/model/split" 9 | "machine-coding-splitwise/pkg" 10 | ) 11 | 12 | func main() { 13 | 14 | // please note that this was what I could come up with given the time limit 15 | // however, please try to explain scope of improvements during the interview 16 | log.Println("inside main") 17 | user1 := &model.User{ 18 | Id: "user_1", 19 | Name: "user_1", 20 | Email: "user_1", 21 | Mobile: "user_1", 22 | } 23 | user2 := &model.User{ 24 | Id: "user_2", 25 | Name: "user_2", 26 | Email: "user_2", 27 | Mobile: "user_2", 28 | } 29 | user3 := &model.User{ 30 | Id: "user_3", 31 | Name: "user_3", 32 | Email: "user_3", 33 | Mobile: "user_3", 34 | } 35 | user4 := &model.User{ 36 | Id: "user_4", 37 | Name: "user_4", 38 | Email: "user_4", 39 | Mobile: "user_4", 40 | } 41 | tHandler := handler.GetTransactionHandler() 42 | tHandler.AddUser(user1) 43 | tHandler.AddUser(user2) 44 | tHandler.AddUser(user3) 45 | tHandler.AddUser(user4) 46 | tHandler.CreateExpense(pkg.EXPENSE_TYPE_EQUAL, expense.Expense{ 47 | Amount: 1000, 48 | PaidBy: user1, 49 | Splits: []*split.Split{ 50 | { 51 | UserId: "user_1", 52 | }, 53 | { 54 | UserId: "user_2", 55 | }, 56 | { 57 | UserId: "user_3", 58 | }, 59 | { 60 | UserId: "user_4", 61 | }, 62 | }, 63 | }) 64 | 65 | tHandler.CreateExpense(pkg.EXPENSE_TYPE_EXACT, expense.Expense{ 66 | Amount: 1250, 67 | PaidBy: user1, 68 | Splits: []*split.Split{ 69 | { 70 | UserId: "user_2", 71 | Amount: 370, 72 | }, 73 | { 74 | UserId: "user_3", 75 | Amount: 880, 76 | }, 77 | }, 78 | }) 79 | // 80 | tHandler.CreateExpense(pkg.EXPENSE_TYPE_EXACT, expense.Expense{ 81 | Amount: 1000, 82 | PaidBy: user3, 83 | Splits: []*split.Split{ 84 | { 85 | UserId: "user_2", 86 | Amount: 300, 87 | }, 88 | { 89 | UserId: "user_1", 90 | Amount: 700, 91 | }, 92 | }, 93 | }) 94 | tHandler.ShowBalance() 95 | } 96 | -------------------------------------------------------------------------------- /golang/pkg/constants.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type ExpenseType int 4 | 5 | const ( 6 | EXPENSE_TYPE_EQUAL ExpenseType = iota 7 | EXPENSE_TYPE_EXACT ExpenseType = iota 8 | EXPENSE_TYPE_PERCENT ExpenseType = iota 9 | ) 10 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /python/classes/EqualExpense.py: -------------------------------------------------------------------------------- 1 | from config import EQUAL_EXPENSE_TYPE_NAME 2 | from classes.Expense import Expense 3 | 4 | 5 | class EqualExpense(Expense): 6 | def __init__(self, amount, paid_by, splits) -> None: 7 | super().__init__(amount, paid_by, splits) 8 | self.type = EQUAL_EXPENSE_TYPE_NAME 9 | -------------------------------------------------------------------------------- /python/classes/ExactExpense.py: -------------------------------------------------------------------------------- 1 | from config import EXACT_EXPENSE_TYPE_NAME 2 | from classes.Expense import Expense 3 | 4 | 5 | class ExactExpense(Expense): 6 | def __init__(self, amount, paid_by, splits) -> None: 7 | super().__init__(amount, paid_by, splits) 8 | self.type = EXACT_EXPENSE_TYPE_NAME 9 | -------------------------------------------------------------------------------- /python/classes/ExactSplit.py: -------------------------------------------------------------------------------- 1 | from config import EXACT_EXPENSE_TYPE_NAME 2 | from classes.Split import Split 3 | 4 | 5 | class ExactSplit(Split): 6 | def __init__(self, user_id, amount) -> None: 7 | super().__init__(user_id, amount) 8 | self.type = EXACT_EXPENSE_TYPE_NAME 9 | -------------------------------------------------------------------------------- /python/classes/Expense.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from classes.Split import Split 3 | 4 | 5 | class Expense: 6 | def __init__(self, amount, paid_by, splits: List[Split]) -> None: 7 | self.amount = amount 8 | self.paid_by = paid_by 9 | self.splits = splits 10 | 11 | # Validate Expenses for both types 12 | -------------------------------------------------------------------------------- /python/classes/Split.py: -------------------------------------------------------------------------------- 1 | class Split: 2 | def __init__(self, user_id, amount) -> None: 3 | self.user_id = user_id 4 | self.amount = amount 5 | -------------------------------------------------------------------------------- /python/classes/TransactionManager.py: -------------------------------------------------------------------------------- 1 | from config import EQUAL_EXPENSE_TYPE_NAME, EXACT_EXPENSE_TYPE_NAME, EXPENSE_TYPES 2 | from typing import List 3 | from classes.User import User 4 | 5 | 6 | class TransactionManager: 7 | def __init__(self) -> None: 8 | self.balance_sheet = {} 9 | 10 | def show_balance_for_user(self, user_id): 11 | if self.balance_sheet.get(user_id) is None: 12 | print('\nNo Balances') 13 | return 14 | else: 15 | balance = self.balance_sheet.get(user_id) 16 | for balance_user, amount in balance.items(): 17 | if amount < 0: 18 | print(f'\n{balance_user} is owed by {user_id}: {abs(amount)}') 19 | else: 20 | print(f'\n{balance_user} owes {user_id}: {amount}') 21 | 22 | return 23 | 24 | def show_all_balances(self): 25 | if len(self.balance_sheet.keys()) == 0: 26 | print("\nNo Balances") 27 | for user_id in self.balance_sheet.keys(): 28 | self.show_balance_for_user(user_id) 29 | 30 | def create_expense(self, paid_by: User, amount, expense_type, users: List[User], users_expenses=[]): 31 | paid_by_user_id = paid_by.get_user_id() 32 | if expense_type not in EXPENSE_TYPES: 33 | print('\nError, expense type dne') 34 | return None 35 | 36 | if expense_type == EQUAL_EXPENSE_TYPE_NAME: 37 | individual_amount = amount / len(users) 38 | 39 | for user in users: 40 | user_id = user.get_user_id() 41 | if paid_by_user_id == user_id: 42 | continue 43 | 44 | if self.balance_sheet[paid_by_user_id].get(user_id) is None: 45 | self.balance_sheet[paid_by_user_id][user_id] = individual_amount 46 | else: 47 | self.balance_sheet[paid_by_user_id][user_id] += individual_amount 48 | 49 | if self.balance_sheet[user_id].get(paid_by_user_id) is None: 50 | self.balance_sheet[user_id][paid_by_user_id] = ( 51 | -1)*individual_amount 52 | else: 53 | self.balance_sheet[user_id][paid_by_user_id] -= individual_amount 54 | 55 | if expense_type == EXACT_EXPENSE_TYPE_NAME: 56 | for i in range(0, len(users)): 57 | user_id = users[i].get_user_id() 58 | individual_amount = users_expenses[i] 59 | if paid_by_user_id == user_id: 60 | continue 61 | if self.balance_sheet[paid_by_user_id].get(user_id) is None: 62 | self.balance_sheet[paid_by_user_id][user_id] = individual_amount 63 | else: 64 | self.balance_sheet[paid_by_user_id][user_id] += individual_amount 65 | 66 | if self.balance_sheet[user_id].get(paid_by_user_id) is None: 67 | self.balance_sheet[user_id][paid_by_user_id] = ( 68 | -1)*individual_amount 69 | else: 70 | self.balance_sheet[user_id][paid_by_user_id] -= individual_amount 71 | 72 | return None 73 | 74 | def add_user(self, user: User): 75 | user_id = user.get_user_id() 76 | if self.balance_sheet.get(user_id) is not None: 77 | print('\nUser already exists in sheet') 78 | else: 79 | self.balance_sheet[user_id] = {} 80 | -------------------------------------------------------------------------------- /python/classes/User.py: -------------------------------------------------------------------------------- 1 | class User: 2 | def __init__(self, id, name, email, mobile) -> None: 3 | self.id = id 4 | self.name = name 5 | self.email = email 6 | self.mobile = mobile 7 | 8 | def get_user_id(self): 9 | return self.id 10 | 11 | def get_user_name(self): 12 | return self.name 13 | 14 | def set_user_id(self, id): 15 | self.id = id 16 | 17 | def set_user_name(self, name): 18 | self.name = name 19 | -------------------------------------------------------------------------------- /python/classes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sagnik-Chaudhuri/machine-coding-splitwise/36c8370460eab89d47ec19f0ac638e110cf4c9b4/python/classes/__init__.py -------------------------------------------------------------------------------- /python/config.py: -------------------------------------------------------------------------------- 1 | EQUAL_EXPENSE_TYPE_NAME = 'EQUAL' 2 | EXACT_EXPENSE_TYPE_NAME = 'EXACT' 3 | 4 | EXPENSE_TYPES = [EQUAL_EXPENSE_TYPE_NAME, EXACT_EXPENSE_TYPE_NAME] 5 | -------------------------------------------------------------------------------- /python/main.py: -------------------------------------------------------------------------------- 1 | from config import EQUAL_EXPENSE_TYPE_NAME, EXACT_EXPENSE_TYPE_NAME 2 | from classes.TransactionManager import TransactionManager 3 | from classes.User import User 4 | 5 | 6 | def main(): 7 | 8 | user1 = User("user_1", 'User 1', 'a', '123') 9 | user2 = User("user_2", 'User 2', 'bcd', '123') 10 | user3 = User("user_3", 'User 3', 'cd', '123') 11 | user4 = User("user_4", 'User 4', 'd', '123') 12 | 13 | transaction_manager = TransactionManager() 14 | transaction_manager.add_user(user1) 15 | transaction_manager.add_user(user2) 16 | transaction_manager.add_user(user3) 17 | transaction_manager.add_user(user4) 18 | # transaction_manager.show_all_balances() 19 | transaction_manager.create_expense(user1, 1000, EQUAL_EXPENSE_TYPE_NAME, [ 20 | user1, user2, user3, user4]) 21 | # transaction_manager.show_all_balances() 22 | # transaction_manager.show_balance_for_user(user1) 23 | transaction_manager.create_expense(user1, 1250, EXACT_EXPENSE_TYPE_NAME, [ 24 | user2, user3], [370, 880]) 25 | transaction_manager.create_expense(user3, 1000, EXACT_EXPENSE_TYPE_NAME, [ 26 | user2, user1], [300, 700]) 27 | # transaction_manager.show_balance_for_user('user_3') 28 | transaction_manager.show_all_balances() 29 | 30 | return None 31 | 32 | 33 | main() 34 | --------------------------------------------------------------------------------