Created
February 5, 2012 01:41
-
-
Save mermshaus/1741846 to your computer and use it in GitHub Desktop.
Calculates who gets how much money back when paying expenses for a group of people
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* When you travel in a group and, for practical reasons, some people pay | |
* expenses for the whole group (hotel rooms, train tickets, ...), this class | |
* will help you to sort out who gets how much money back in the end. | |
*/ | |
namespace foo; | |
use Exception; | |
class Calc | |
{ | |
/** @var array */ | |
protected $names = array(); | |
/** @var array */ | |
protected $transactions = array(); | |
/** | |
* | |
* @param array $lines Lines of formatted input | |
* @throws Exception | |
*/ | |
public function import($lines) | |
{ | |
foreach ($lines as $line) { | |
$line = trim($line); | |
// Skip empty lines | |
if ($line === '') { | |
continue; | |
} | |
// Skip comments | |
if (substr($line, 0, 1) === ';') { | |
continue; | |
} | |
$elements = preg_split('/\s{2,}/', $line); | |
if (count($elements) !== 4) { | |
throw new Exception(sprintf('Line not well-formed: %s'), $line); | |
} | |
$this->process($elements); | |
} | |
if (isset($this->names['*'])) { | |
unset($this->names['*']); | |
} | |
$this->names = array_keys($this->names); | |
$this->expandMacros(); | |
} | |
public function debug() | |
{ | |
echo "Events:\n"; | |
echo "=======\n\n"; | |
foreach ($this->transactions as $trans) { | |
printf("% 10s lends % 10s % 5s (%s)\n", | |
$trans['from'], $trans['to'], $trans['amount'], $trans['description']); | |
} | |
echo "\n"; | |
} | |
/** | |
* | |
* @param array $elements | |
*/ | |
protected function process($elements) | |
{ | |
list($from, $to, $amount, $desc) = $elements; | |
if (strpos($to, ',') !== false) { | |
$tos = explode(',', $to); | |
$to = array(); | |
foreach ($tos as $t) { | |
$t = trim($t); | |
if ($t === '') { | |
continue; | |
} | |
$to[] = $t; | |
} | |
} else { | |
$to = array($to); | |
} | |
$amount = (float) $amount; | |
$this->names[$from] = true; | |
foreach ($to as $t) { | |
$this->names[$t] = true; | |
} | |
$this->transactions[] = array( | |
'from' => $from, | |
'to' => $to, | |
'amount' => $amount, | |
'description' => $desc | |
); | |
} | |
protected function expandMacros() | |
{ | |
for ($i = 0; $i < count($this->transactions); $i++) { | |
$trans = &$this->transactions[$i]; | |
if ($trans['to'] === array('*')) { | |
$trans['to'] = $this->names; | |
} | |
if ($trans['description'] === '"') { | |
$trans['description'] = $this->transactions[$i - 1]['description']; | |
} | |
} | |
unset($trans); | |
$newTrans = array(); | |
foreach ($this->transactions as $trans) { | |
if (count($trans['to']) > 1) { | |
foreach ($trans['to'] as $to) { | |
$newTrans[] = array( | |
'from' => $trans['from'], | |
'to' => $to, | |
'amount' => $trans['amount'] / count($trans['to']), | |
'description' => $trans['description'] | |
); | |
} | |
} else { | |
$newTrans[] = array( | |
'from' => $trans['from'], | |
'to' => $trans['to'][0], | |
'amount' => $trans['amount'], | |
'description' => $trans['description'] | |
); | |
} | |
} | |
$this->transactions = $newTrans; | |
} | |
public function getNames() | |
{ | |
return $this->names; | |
} | |
public function getExpensesByName($name, $withoutOwn = false) | |
{ | |
$expenses = 0.0; | |
foreach ($this->transactions as $trans) { | |
if ($trans['from'] === $name) { | |
if ($withoutOwn && $trans['to'] === $name) { | |
continue; | |
} | |
$expenses += $trans['amount']; | |
} | |
} | |
return $expenses; | |
} | |
public function getDebtsByName($name, $withoutOwn = false) | |
{ | |
$expenses = 0.0; | |
foreach ($this->transactions as $trans) { | |
if ($trans['to'] === $name) { | |
if ($withoutOwn && $trans['from'] === $name) { | |
continue; | |
} | |
$expenses += $trans['amount']; | |
} | |
} | |
return $expenses; | |
} | |
public function getDebtorsByName($name, $withoutOwn = false) | |
{ | |
$debtors = array(); | |
foreach ($this->transactions as $trans) { | |
if ($trans['from'] === $name) { | |
if ($withoutOwn && $trans['to'] === $name) { | |
continue; | |
} | |
if (!isset($debtors[$trans['to']])) { | |
$debtors[$trans['to']] = 0.0; | |
} | |
$debtors[$trans['to']] += $trans['amount']; | |
} | |
} | |
return $debtors; | |
} | |
} | |
$demo = <<<EOT | |
; Who loaned money to whom? | |
; | |
; * := "everybody", expands to (Tom, Bob, Mia) in this case | |
; | |
; " := "duplicate previous description" | |
; | |
; Mia paid for the groups' train tickets. | |
; Bob paid for Tom's cappuccino. | |
; Tom paid for Mia's and Bob's lunch. | |
; Bob paid for the groups' hotel rooms. | |
; | |
;From To Amount Description | |
;------------------------------------------------- | |
Mia * 12.00 train tickets | |
Bob Tom 3.10 cappuccino | |
Tom Mia 10.30 lunch | |
Tom Bob 11.70 " | |
Bob * 97.20 hotel rooms | |
EOT; | |
$lines = explode("\n", $demo); | |
$c = new Calc(); | |
$c->import($lines); | |
$c->debug(); | |
echo "Money loaned:\n"; | |
echo "=============\n\n"; | |
$sum = 0.0; | |
foreach ($c->getNames() as $name) { | |
printf("% 10s : %s\n", $name, $c->getExpensesByName($name)); | |
/*foreach ($c->getDebtorsByName($name) as $debtor => $amount) { | |
printf(" % 10s : %s\n", $debtor, $amount); | |
}*/ | |
$sum += $c->getExpensesByName($name); | |
} | |
printf("SUM: %s\n", $sum); | |
echo "\n"; | |
echo "Money borrowed:\n"; | |
echo "===============\n\n"; | |
$sum = 0.0; | |
foreach ($c->getNames() as $name) { | |
printf("% 10s : %s\n", $name, $c->getDebtsByName($name)); | |
$sum += $c->getDebtsByName($name); | |
} | |
printf("SUM: %s\n", $sum); | |
echo "\n"; | |
echo "Balances:\n"; | |
echo "=========\n\n"; | |
$sum = 0.0; | |
foreach ($c->getNames() as $name) { | |
printf("% 10s : %s\n", $name, $c->getExpensesByName($name) - $c->getDebtsByName($name)); | |
$sum += $c->getExpensesByName($name) - $c->getDebtsByName($name); | |
} | |
printf("SUM: %s\n", round($sum, 2)); | |
echo "\n"; | |
exit; | |
// This doesn't work so well, yet. The idea is to print a list of the least | |
// number of transactions necessary to equalize all balances | |
echo "Transactions:\n"; | |
echo "==============\n\n"; | |
$balances = array(); | |
foreach ($c->getNames() as $name) { | |
$balances[$name] = $c->getExpensesByName($name) - $c->getDebtsByName($name); | |
} | |
arsort($balances); | |
foreach ($balances as $key => $value) { | |
if ($value > 0.0) { | |
$poss[$key] = $value; | |
} else { | |
$negs[$key] = $value; | |
} | |
} | |
foreach ($poss as $key => $value) { | |
while ($value > 0.0) { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment