我有此函数,用于计算收益递减的值。它计算从输入值中减去一个不断增加的值的频率,并返回相减的次数。目前,它是通过无限循环迭代实现的:
// inputValue is our parameter. It is manipulated in the method body.
// step counts how many subtractions have been performed so far. It is also our returned value.
// loss is the value that is being subtracted from the inputValue at each step. It grows polynomially with each step.
public int calculate(int inputValue) {
for (int step = 1; true; step++) {// infinite java for-each loop
int loss = (int) (1 + 0.0006 * step*step + 0.2 * step);
if (inputValue > loss) {
inputValue -= loss;
} else {
return step;
}
}
}
此功能在大型应用程序的各个位置使用,有时在对性能要求较高的代码中使用。我希望将其重构为不再需要循环的方式。
我相当确定可以通过某种方式更直接地计算结果。但是我的数学技能似乎不足以做到这一点。
有人可以告诉我一个可以产生相同结果而无需循环或递归的函数吗?如果重构的代码对于极值和极端情况可能会产生不同的结果,则可以。负输入不需要考虑。
谢谢大家。
答案 0 :(得分:2)
我认为您不可以使代码更快地保留确切的逻辑。特别是您很难在以下位置模拟舍入
int loss = (int) (1 + 0.0006 * step*step + 0.2 * step);
如果这是您的业务逻辑要求而不是错误,那么我认为您不能做得更好。另一方面,如果您真正想要的是类似的东西(根据我假设您使用Java的语法):
public static int calculate_double(int inputValue) {
double value = inputValue;
for (int step = 1; true; step++) {// infinite java for-each loop
double loss = (1 + 0.0006 * step * step + 0.2 * step); // no rounding!
if (value > loss) {
value -= loss;
} else {
return step;
}
}
}
即同样的逻辑,但每一步都没有四舍五入,那么就有一些希望。
注意:很遗憾,此舍入确实有所作为。例如,根据我的测试,calculate
范围内的每个calculate_double
的{{1}}和inputValue
的输出都略有不同(有时甚至比{{1}大) },例如[4, 46465]
是+1
vs inputValue = 1000
)。对于更大的calculate = 90
,结果更加一致。例如,对于calculate_double = 88
/ inputValue
的结果,差异范围仅为519
。对于每个结果,仍然会有一定范围的不同结果。
首先,在给定最大值520
不取整的情况下([55294, 55547]
),loss
的总和具有一个封闭式:
step
因此从理论上找到这样的n
可以使sum(n) = n + 0.0006*n*(n+1)*(2n+1)/6 + 0.2*n*(n+1)/2
通过求解具有closed formula的三次方程n
并然后检查sum(n) < inputValue < sum(n+1)
之类的值来完成和sum(x) = inputValue
。但是,其背后的数学运算有点复杂,因此我没有走那条路。
还请注意,由于floor(x)
的范围有限,因此从理论上讲,即使是该算法的实现也是ceil(x)
(因为它永远不会比计算int
花费更多的步骤,不变)。因此,您真正想要的只是大幅提高速度。
不幸的是,系数O(1)
和calculate(Integer.MAX_VALUE)
很小,足以使不同的0.0006
的总和的主要部分不同。仍然可以使用二进制搜索获得更好的性能:
0.2
注意:n
是原始逻辑的副本,它在减去最后一个static int sum(int step) {
// n + 0.2 * n*(n+1)/2 + 0.0006 * n*(n+1)*(2n+1)/6
// ((0.0001*(2n+1) + 0.1) * (n+1) + 1) * n
double s = ((0.0001 * (2 * step + 1) + 0.1) * (step + 1) + 1) * step;
return (int) s;
}
static int calc_bin_search2(int inputValue) {
int left = 0;
// inputValue / 2 is a safe estimate, the answer for 100 is 27 or 28
int right = inputValue < 100 ? inputValue : inputValue / 2;
// for big inputValue reduce right more aggressively before starting the binary search
if (inputValue > 1000) {
while (true) {
int test = right / 8;
int tv = sum(test);
if (tv > inputValue)
right = test;
else {
left = test;
break;
}
}
}
// just usual binary search
while (true) {
int mid = (left + right) / 2;
int mv = sum(mid);
if (mv == inputValue)
return mid;
else if (mid == left) {
return mid + 1;
} else if (mv < inputValue)
left = mid;
else
right = mid;
}
}
之后返回一个return mid + 1
。
在我的测试中,该实现与step
的输出匹配,并且在loss
下与calculate_double
的性能大致相同,对于inputValue
左右的值,速度提高了50倍,而x200 1000