Skip to content

Instantly share code, notes, and snippets.

@mermshaus
Created February 5, 2012 01:41
Show Gist options
  • Save mermshaus/1741846 to your computer and use it in GitHub Desktop.
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
<?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