返回

PHP 使用循环分配按费率区间计算时长

php

使用 Do While 循环收集基于用户输入费率

本篇文章将探讨如何使用 do while 循环,根据用户输入的总时长,将时间分配到不同的费率区间。 这个问题在处理按时计费或加班费率等场景时经常出现。我们将分析问题的原因,并提供几种可行的解决方案,解决精度和循环逻辑方面的问题。

问题分析

问题核心在于如何将用户输入的总时长(包含小数部分),按照预定义的费率阶梯(after 字段)准确分配。 现有的代码主要有两个问题:

  1. 精度丢失: 代码使用整数 1 来递减 totalTime ,这会导致无法精确处理小数部分。 例如, 15.5 小时将被分割成 15 个小时。
  2. 循环逻辑: 在递减 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);


操作步骤:

  1. 将示例代码复制到PHP环境中。
  2. 替换 $totalTime 变量的值为需要处理的总时长。
  3. 定义一个包含after作为键的 $rates 数组, 类似上面的例子。
  4. 运行代码,查看分配结果。

解决方案二:利用 while 循环并反向遍历

此方案不再使用 do while 循环,改为使用标准的 while 循环,并结合 array_reverse 进行反向遍历,处理边界情况。

原理:

  • 先反转费率数组, 让 after 较大的费率先被处理,这样更容易按照时间降序分配。
  • 使用 while 循环递减 $totalTime ,条件为 $totalTime > 0
  • 反向遍历 rates 数组,查找匹配的 after 值。
  • 如果找到了匹配的值, 给 units 加上分配的 step(步长)。如果 totalTime 小于 当前遍历到的after 值,且没有 units 分配,给最小 afterunitsstep
  • 使用 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);
?>

操作步骤:

  1. 复制上述代码,放入 php 环境中运行。
  2. 设置你需要的 $totalTime,然后运行。

安全建议

在实际应用中,务必对用户输入进行验证,以避免不必要的错误或潜在的安全问题。 可以添加一下校验:

  1. 确保totalTime 为有效的数字。
  2. 验证rates 数组是否为空或结构不符合要求。
  3. 对于重要场景,可以记录分配的日志或事件,方便后期分析或追溯。

希望本篇文章可以帮助解决你所遇到的问题。使用任何方法之前都应结合自己的场景测试是否符合实际需求。