返回 解决方案二:利用
PHP 使用循环分配按费率区间计算时长
php
2024-12-29 04:57:28
使用 Do While 循环收集基于用户输入费率
本篇文章将探讨如何使用 do while
循环,根据用户输入的总时长,将时间分配到不同的费率区间。 这个问题在处理按时计费或加班费率等场景时经常出现。我们将分析问题的原因,并提供几种可行的解决方案,解决精度和循环逻辑方面的问题。
问题分析
问题核心在于如何将用户输入的总时长(包含小数部分),按照预定义的费率阶梯(after
字段)准确分配。 现有的代码主要有两个问题:
- 精度丢失: 代码使用整数
1
来递减totalTime
,这会导致无法精确处理小数部分。 例如,15.5
小时将被分割成 15 个小时。 - 循环逻辑: 在递减
totalTime
之后,代码仅仅使用了>= 1
作为循环条件,这并没有充分考虑到小于1的小时值处理。在小于 1 的情况下,无法完成有效的小数分割逻辑。
解决方案一:精确递减与边界判断
这个方案通过使用更小的递减单位(例如0.1
)并调整循环条件来解决精度问题。同时,针对时间剩余不足以分配一整个单位时的情况进行处理。
原理:
- 使用
0.1
作为递减单位,使时间计算更加精确。 - 修改
do while
循环条件为$totalTime > 0
,以确保所有的小时值都被处理,包括小数部分。 - 调整判断条件,正确匹配合适的费率阶梯。
- 如果小时值小于费率分割点
after
则匹配到下一个较小的值
代码示例:
$totalTime = 15.5; // 用户输入的总时长,可以是任意小数
$rates = [
1 => ['after' => 0, 'units' => 0, 'rate' => 0, 'rate_id' => 1],
2 => ['after' => 8, 'units' => 0, 'rate' => 12, 'rate_id' => 2],
3 => ['after' => 12, 'units' => 0, 'rate' => 15, 'rate_id' => 3]
];
$rates = collect($rates)->keyBy('after')->toArray();
do {
$assigned = false;
foreach ($rates as $after => &$rate) {
if ($totalTime >= $after )
{
$rate['units'] = $rate['units'] + 0.1;
$assigned = true;
break;
}
}
if (!$assigned)
{
foreach ($rates as &$rate) {
$rate['units'] = $rate['units'] + 0.1;
break;
}
}
$totalTime -= 0.1;
$totalTime = round($totalTime, 1);
} while ($totalTime > 0);
print_r($rates);
操作步骤:
- 将示例代码复制到PHP环境中。
- 替换
$totalTime
变量的值为需要处理的总时长。 - 定义一个包含
after
作为键的$rates
数组, 类似上面的例子。 - 运行代码,查看分配结果。
解决方案二:利用 while
循环并反向遍历
此方案不再使用 do while
循环,改为使用标准的 while
循环,并结合 array_reverse
进行反向遍历,处理边界情况。
原理:
- 先反转费率数组, 让
after
较大的费率先被处理,这样更容易按照时间降序分配。 - 使用
while
循环递减$totalTime
,条件为$totalTime > 0
。 - 反向遍历
rates
数组,查找匹配的after
值。 - 如果找到了匹配的值, 给
units
加上分配的step
(步长)。如果totalTime
小于 当前遍历到的after
值,且没有units
分配,给最小after
的units
加step
值 - 使用
round()
来规避小数精度的问题。
代码示例:
<?php
$totalTime = 15.5; // 用户输入的总时长
$rates = [
1 => ['after' => 0, 'units' => 0, 'rate' => 0, 'rate_id' => 1],
2 => ['after' => 8, 'units' => 0, 'rate' => 12, 'rate_id' => 2],
3 => ['after' => 12, 'units' => 0, 'rate' => 15, 'rate_id' => 3]
];
$step = 0.1;
$rates = collect($rates)->sortBy('after')->toArray();
$ratesReverse = array_reverse($rates);
while ($totalTime > 0) {
$assigned = false;
foreach ($ratesReverse as $rate)
{
if($totalTime >= $rate['after']) {
$rates[$rate['after']]['units'] = $rates[$rate['after']]['units'] + $step;
$assigned = true;
break;
}
}
if (!$assigned) {
foreach ($rates as $rate) {
$rates[$rate['after']]['units'] = $rates[$rate['after']]['units'] + $step;
break;
}
}
$totalTime -= $step;
$totalTime = round($totalTime, 1);
}
print_r($rates);
?>
操作步骤:
- 复制上述代码,放入 php 环境中运行。
- 设置你需要的
$totalTime
,然后运行。
安全建议
在实际应用中,务必对用户输入进行验证,以避免不必要的错误或潜在的安全问题。 可以添加一下校验:
- 确保
totalTime
为有效的数字。 - 验证
rates
数组是否为空或结构不符合要求。 - 对于重要场景,可以记录分配的日志或事件,方便后期分析或追溯。
希望本篇文章可以帮助解决你所遇到的问题。使用任何方法之前都应结合自己的场景测试是否符合实际需求。