PHP中按指定等分数量和步长约束生成数值序列
在PHP业务开发中,我们经常会遇到需要生成数值序列的场景,比如生成价格区间、时间分段、数据采样点等。有时候需求不仅仅是简单的等差数列,还会同时限制两个维度:一是序列的总等分数量,二是相邻两个数值之间的最大步长。如果单纯用其中一个条件生成,很容易不符合另一个条件的约束,本文就介绍一种同时兼顾这两个约束的数值序列生成方案。
需求场景说明
假设我们需要生成一个从起始值到结束值的数值序列,同时需要满足两个要求:
- 序列的等分数量为N,也就是最终序列的元素个数要比N多1(比如等分2份,得到3个数值)
- 相邻两个数值之间的步长不能超过指定的最大值M
比如起始值是0,结束值是10,要求等分3份,最大步长不超过4。如果只按等分3份计算,步长是(10-0)/3≈3.33,满足步长≤4的要求,最终序列是[0, 3.33, 6.66, 10];如果结束值是15,等分3份的步长是5,超过了最大步长4,这时候就需要调整等分数量,让步长不超过4,同时尽可能接近要求的等分数量。
实现思路
核心逻辑可以分为以下几步:
- 首先计算仅按指定等分数量生成时,理论上的步长值
- 判断理论步长是否超过最大步长约束,如果未超过,直接按等分数量生成序列即可
- 如果理论步长超过最大步长,就用最大步长作为实际步长,重新计算需要的等分数量,保证步长符合要求
- 最后根据计算后的起始值、结束值、实际步长生成完整的数值序列
完整代码实现
下面是封装好的生成函数,支持浮点型数值,并且可以指定保留的小数位数:
<?php
/**
* 按指定等分数量和步长约束生成数值序列
* @param float $start 序列起始值
* @param float $end 序列结束值
* @param int $splitCount 要求的等分数量
* @param float $maxStep 最大允许步长
* @param int $precision 数值保留小数位数,默认2位
* @return array 生成的数值序列
*/
function generateConstrainedSequence(float $start, float $end, int $splitCount, float $maxStep, int $precision = 2): array
{
// 输入校验,起始值不能大于结束值
if ($start > $end) {
throw new InvalidArgumentException("起始值不能大于结束值");
}
// 输入校验,等分数量必须为正整数
if ($splitCount <= 0) {
throw new InvalidArgumentException("等分数量必须为正整数");
}
// 输入校验,最大步长必须为正数
if ($maxStep <= 0) {
throw new InvalidArgumentException("最大步长必须为正数");
}
// 计算总区间长度
$totalRange = $end - $start;
// 如果起始值等于结束值,直接返回仅包含起始值的数组
if ($totalRange == 0) {
return [round($start, $precision)];
}
// 计算仅按指定等分数量的理论步长
$theoryStep = $totalRange / $splitCount;
// 判断理论步长是否超过最大步长
if ($theoryStep <= $maxStep) {
// 未超过,按指定等分数量生成
$actualStep = $theoryStep;
$actualSplitCount = $splitCount;
} else {
// 超过,按最大步长计算实际需要的等分数量,向上取整保证步长不超过最大值
$actualSplitCount = (int)ceil($totalRange / $maxStep);
$actualStep = $totalRange / $actualSplitCount;
}
// 生成序列
$sequence = [];
for ($i = 0; $i <= $actualSplitCount; $i++) {
$currentValue = $start + $i * $actualStep;
// 防止浮点计算误差,最后一个值直接用结束值
if ($i == $actualSplitCount) {
$currentValue = $end;
}
$sequence[] = round($currentValue, $precision);
}
return $sequence;
}
// 示例1:理论步长未超过最大步长的情况
$seq1 = generateConstrainedSequence(0, 10, 3, 4);
echo "示例1结果:";
print_r($seq1);
// 示例2:理论步长超过最大步长的情况
$seq2 = generateConstrainedSequence(0, 15, 3, 4);
echo "示例2结果:";
print_r($seq2);
// 示例3:起始值等于结束值的情况
$seq3 = generateConstrainedSequence(5, 5, 2, 3);
echo "示例3结果:";
print_r($seq3);代码说明
上面的函数首先做了基础的输入校验,避免非法参数导致逻辑异常。核心的步长判断逻辑中,当理论步长超过最大步长时,用ceil()函数向上取整计算实际等分数量,这样可以保证实际步长不会超过最大步长约束。生成序列的循环中,最后一个值直接取结束值,避免了浮点计算累加带来的精度误差,最后通过round()函数统一保留指定的小数位数。
运行结果说明
执行上面的示例代码,输出结果如下:
示例1结果:Array
(
[0] => 0
[1] => 3.33
[2] => 6.67
[3] => 10
)
示例2结果:Array
(
[0] => 0
[1] => 3.75
[2] => 7.5
[3] => 11.25
[4] => 15
)
示例3结果:Array
(
[0] => 5
)示例1中总区间10,等分3份理论步长约3.33,小于最大步长4,所以按3等分生成,得到4个数值;示例2中总区间15,等分3份理论步长5,超过最大步长4,所以实际等分数量计算为ceil(15/4)=4,得到5个数值,步长为3.75,符合步长约束;示例3起始值和结束值相同,直接返回仅包含该值的数组。
扩展建议
如果业务场景中还需要支持起始值大于结束值的反向序列生成,可以额外增加方向判断的逻辑,当起始值大于结束值时,步长计算为负数即可;另外如果需要生成的序列包含整数要求,可以在生成后对数值做取整处理,根据实际业务需求调整即可。