我正在努力解决您可能遇到的以下现实问题:
你和一些朋友共进晚餐,你们都同意平均分摊账单。除了账单终于到来之外,你发现不是每个人都有足够的现金(如果有的话,便宜的混蛋)。
所以,你们中的一些人比其他人付出更多...之后你们回家并试着决定“谁欠谁的数额?”。
这个,我正在努力解决算法问题。公平:))
起初它似乎很容易,但是我已经陷入了四舍五入的困境,我觉得这完全是一个失败者;)关于如何解决这个问题的任何想法?
编辑:一些python代码显示我的困惑
>>> amounts_paid = [100, 25, 30]
>>> total = sum(amounts_paid)
>>> correct_amount = total / float(len(amounts_paid))
>>> correct_amount
51.666666666666664
>>> diffs = [amnt-correct_amount for amnt in amounts_paid]
>>> diffs
[48.333333333333336, -26.666666666666664, -21.666666666666664]
>>> sum(diffs)
7.1054273576010019e-015
理论上,差异的总和应为零,对吧?
它的另一个例子:)
>>> amounts_paid = [100, 50, 150]
>>> total = sum(amounts_paid)
>>> correct_amount = total / float(len(amounts_paid))
>>> correct_amount
100.0
>>> diffs = [amnt-correct_amount for amnt in amounts_paid]
>>> diffs
[0.0, -50.0, 50.0]
>>> sum(diffs)
0.0
答案 0 :(得分:9)
其中。问题已经解决了。很多次。
“理论上,差异的总和应该为零,对吗?”
是。但是,由于您使用了float
,因此当人数不是2的幂时,就会出现问题。
从不。使用。 float
对于。财务。
<强>从不强>
始终。使用。 decimal
对于。财务。
始终强>
答案 1 :(得分:5)
诀窍是将每个人视为一个单独的帐户。
您可以轻松确定(从原始账单中)每个人应支付多少钱。将此设置为每个人的负数。接下来,通过将支付的金额添加到其帐户中,记录每个人已支付的金额。在这一点上,多付(贷款人)的人将获得正余额,而欠款(借款人)的人将有负余额。
借款人欠每个贷方的钱没有一个正确的答案,除非在只有一个贷方的明显情况下。借款人支付的金额可以转给任何贷方。只需将金额加到借款人的总金额中,然后从收到付款的贷方中扣除金额。
当所有帐户都达到零时,每个人都已付清。
修改(以回应评论):
我认为我的问题在于这个数量并不总是可以被整除的事实,所以提出一个优雅地处理这个问题的算法似乎再次让我失望了。试。
在处理美元和美分时,没有100%干净的方法来处理舍入。有些人会比其他人多支付1美分。公平的唯一方法是随机分配额外的0.01美元(根据需要)。当通过分割账单计算“欠款”时,这只会进行一次。它有时有助于将货币价值存储为美分,而不是美元(例如12.34美元将存储为1234美元)。这使您可以使用整数而不是浮点数。
为了分配额外的分数,我会做以下事情:
total_cents = 100 * total;
base_amount = Floor(total_cents / num_people);
cents_short = total_cents - base_amount * num_people;
while (cents_short > 0)
{
// add one cent to a random person
cents_short--;
}
注意:“随机”分配便士的最简单方法是将第一个额外分配给第一个人,第二个分配给第二个,等等。这只会成为一个问题,如果你总是以相同的顺序输入相同的人。
答案 2 :(得分:2)
我不是蟒蛇人,但我发现这是一个有趣的问题=)这是我的解决方案。开发时间~45分钟。我写的是干净的perl ...应该很容易移植。
~/sandbox/$ ./bistro_math.pl
Anna owes Bill 7.57
Anna owes Mike 2.16
John owes Mike 2.62
~/sandbox/$ cat bistro_math.pl
#!/usr/bin/perl
use strict;
use warnings;
### Dataset.
### Bill total: 50.00
### Paid total: 50.00
my @people = (
{ name => 'Bill', bill => 5.43, paid => 13.00 },
{ name => 'Suzy', bill => 12.00, paid => 12.00 },
{ name => 'John', bill => 10.62, paid => 8.00 },
{ name => 'Mike', bill => 9.22, paid => 14.00 },
{ name => 'Anna', bill => 12.73, paid => 3.00 },
);
### Calculate how much each person owes (or is owed: -/+)
calculate_balances(\@people);
### Tally it all up =) This algorithm is designed to have bigger lenders
### paid back by the fewest number of people possible (they have the least
### hassle, since they were the most generous!).
sub calculate_balances {
my $people = shift;
### Use two pools
my @debtors;
my @lenders;
foreach my $person (@$people) {
### Ignore people who paid exactly what they owed.
$person->{owes} = $person->{bill} - $person->{paid};
push @debtors, $person if ($person->{owes} > 0);
push @lenders, $person if ($person->{owes} < 0);
}
LENDERS: foreach my $lender (@lenders) {
next if ($lender->{owes} >= 0);
DEBTORS: foreach my $debtor (@debtors) {
next if ($debtor->{owes} <= 0);
my $payment = ($lender->{owes} + $debtor->{owes} < 0)
? abs $debtor->{owes}
: abs $lender->{owes};
$lender->{owes} += $payment;
$debtor->{owes} -= $payment;
$debtor->{pays} = [] if (not exists $debtor->{pays});
print "$debtor->{name} owes $lender->{name} $payment\n";
next LENDERS if ($lender->{owes} >= 0);
}
}
}
exit;
~/sandbox/$
答案 3 :(得分:1)
你知道每个人的欠款总数,以及谁是卖空者。以每个人都做的那个总时间为例,从最高支付者到最低支付者,从支付更多费用的人那里得出结论。
答案 4 :(得分:1)
您所看到的“问题”与浮点数的二进制表示有关,如here所述。无论如何, 7.1054273576010019e-015 是一个很小的数字,所以如果你将结果四舍五入到最接近的分数,你就不会有任何问题。