你能简化这个算法吗?
Posted
技术标签:
【中文标题】你能简化这个算法吗?【英文标题】:Can you simplify this algorithm? 【发布时间】:2010-09-27 17:02:01 【问题描述】:一个给数学家的。这已经传遍了整个办公室,我们想看看谁能提出更好的优化版本。
(((a+p) <= b) && (a == 0 || a > 1) && (b >= p)) &&
((b - (a + p) == 0) || (b - (a + p) > 1))
编辑:所有数据都是正整数
编辑:更好 == 为简单而重构
【问题讨论】:
变量有什么限制吗?例如。整数类型,隐式int->bool转换等? 这些变量是什么类型的?诠释?浮动? 那不是算法。这是一个函数。 亚当,您现在可能要为数百个失去的工作时间负责。 =) 只想插入:codegolf.stackexchange.com 我怀疑这个问题更适合那里:) 【参考方案1】:(a + p <= b) && (a != 1) && (b - a - p != 1);
【讨论】:
通过了我的测试(0-200 的所有可能组合) 你如何从 (b >= p)) && ((b - (a + p) != 1) 到 (b - a - p != 1) ...我可以看到最后的条款是等价的,但你如何删除 b>=p? a+p=p。 a+p b>=p+a hmm...我知道 a+b =a+p 但我花了一段时间才明白这意味着 b>=p...事实 a>=0 是的,我明白你的意思。 如果你使用脚本而不是依赖编译器,你可以通过第一和第三部分将其加速到 OR... (a != 1) && ((b > (a+ p+1)) || (b == (a+p))))【参考方案2】:如果公式有效并且来自您的业务规则,则没有真正需要简化它。编译器可能比我们更了解如何优化公式。
您唯一应该做的就是使用更好的变量名称来反映业务逻辑。
在对它们进行单元测试之前,请注意应用任何建议的解决方案。
【讨论】:
我同意这一点。特别是如果公式来自某种科学论文。您希望这些类型的公式能够逐字阅读,因此您可以引用源代码。 嗯。 “不使用大脑”?简化过于复杂的条件不是“优化”,而是重构/提高清晰度。 不编译怎么办?它可能位于未优化的脚本中。 @Brian - 资料来源很好,但科学论文通常会在论文中给出最简单的方程形式。一般来说,这些公式在编写代码时往往会变长一些。 当然。如果业务规则本身在当前配置中得到有意义的反映,这并不过分复杂(这对于更好的变量名来说很明显......)【参考方案3】:通过引入更多表示每个表达式含义的局部变量来简化重构。如果不知道 a、b 和 p 的含义,我们很难做到这一点。
【讨论】:
我正在阅读 Bob 大叔的 Clean Code atm,这段代码可能是重构方法之前的图片之一。 很好的答案。用好名字构建函数——你的就是正确的答案。 更改 a、b 和 p 的名称并没有消除简化这个不必要的复杂条件表达式的需要;实际上,添加中间变量会使简化更加困难。 我强烈怀疑添加中间变量会使理解变得更简单——这有助于之后的简化。请注意,我不只是建议更改变量的名称 - 提取常用表达式并给它们一个有意义的名称。 但是提取常用表达式并将它们封装在另一个名称后面会删除消除子表达式的能力...【参考方案4】:b >= p && b != p+1
编辑:好的,这不起作用,但这个可以:
a != 1 && b >= a+p && b-a-p != 1
【讨论】:
你是如何去掉“(a == 0 || a > 1)”部分的? @TomWij:是的,事实证明 a 并不是那么无关紧要 =))【参考方案5】:(a!=1) && ((b==a+p) || (b>1+a+p))
它可能不是最简单的,但应该是最易读的。
【讨论】:
我认为所有的答案都应该有经过 Tom Wizmap 测试的证明。【参考方案6】:我不会在那个表达式中做所有的数学运算。比如 b - ( a + p ) 被计算两次。如果可能,请将它们拆分为变量。
另外,编写波兰表示树可能会帮助您优化它,您看到两次的所有内容都可以重复使用。
【讨论】:
【参考方案7】:由于它们都是正整数,因此可以删除很多重复:
所以第一步,
(((a+p) <= b) && (a == 0 || a > 1) && (b >= p)) && ((b - (a + p) == 0) || (b - (a + p) > 1))
变成
((a+p) <= b) && (a != 1) && (b >= p)) && ((b - (a + p) != 1)
为了清楚起见,这只是将(foo == 0 || foo > 1)
模式替换为foo != 1
该模式在上面出现了两次,一次是 foo = a,一次是 foo = (b - (a+p))
【讨论】:
怎么样?两者都应该为假,因为它们具有相同的 b>=p 条件,即为假,并使主要的 && 条件为假。 @TomWij: a 和 b 不能为 = 0,它们都是正整数(表示 > 0) 由于原始代码包含“a == 0”,所以我认为可以安全地假设他的意思是“非负整数”,而不是“正整数”【参考方案8】:由于整数是无符号的,所以 (a==0 || a>1) 可以代替 (a !=1)。
通过第一遍,您可以将其简化为:
uint sum = a + p;
return ((sum <= b) && (a != 1) && (b >= p)) && (b - sum != 1);
此外,如果您能够为变量赋予更有意义的名称,它的可读性会更高。例如,如果 a 和 p 是压力,则 a+p 可以替换为 PressureSum。
【讨论】:
不,它不会失败。我刚刚测试了它。两种实现都在 Function(0, 2, 1) 上返回 false。请收回该评论。 @TomWij:a 不能为 = 0,它是一个正整数(表示 > 0)【参考方案9】:s = a + p
b >= s && a != 1 && b - s - 1 > 0
选中,返回与问题相同的布尔值。
我曾经检查过的程序:(写得很开心)
#include <iostream>
using namespace std;
typedef unsigned int uint;
bool condition(uint a, uint b, uint p)
uint s = a + p;
return uint( b >= s && a != 1 && b - s - 1 > 0 )
== uint( (((a+p) <= b) && (a == 0 || a > 1) && (b >= p))
&& ((b - (a + p) == 0) || (b - (a + p) > 1)) );
void main()
uint i = 0;
uint j = 0;
uint k = 0;
const uint max = 50;
for (uint i = 0; i <= max; ++i)
for (uint j = 0; j <= max; ++j)
for (uint k = 0; k <= max; ++k)
if (condition(i, j, k) == false)
cout << "Fails on a = " << i << ", b = " << j;
cout << ", p = " << k << endl;
int wait = 0;
cin >> wait;
【讨论】:
使用您的测试夹具,我的解决方案没有失败,尽管您评论说它在 0,2,1 失败 比“b - s - 1 > 0”更清晰。 2,4,2 怎么样:4-(2+2)-1 = -1。你在这里依赖溢出吗?很不错,但是如果完成了范围检查呢? (顺便说一句,这就是为什么 b-s>1 不起作用的原因,因为 4-(2+2)=0 对 (b - (a + p) == 0) 有效。) 呃,人们对我的看法太容易投反对票了。我的回答没有错…… 不会强制转换为单位消除负面结果吗?从而可能隐藏错误?【参考方案10】:bap = b - (a + p)
bap >= 0 && bap != 1 && a != 1
编辑:现在我得到了 -2 的诚实尝试来提供帮助,以及在我看来是一个有效的答案。对于会用Python的你,这里有两个函数,一个是问题,一个是我的答案:
def question(a, b, p):
return (((a+p) <= b) and (a == 0 or a > 1) and (b >= p)) or ((b - (a + p) == 0) or (b - (a + p) > 1))
def answer(a, b, p):
bap = b - (a + p)
return bap >= 0 and bap != 1 and a != 1
【讨论】:
在 a = 0、b = 0、p = 1 时失败;填写您的等式和问题,它不会给出相同的布尔值。 这仅在 bap 是有符号整数时才有效。否则 bap 将翻转到 UINT_MAX。 对我来说,这两种情况都返回 False。 是的,我刚刚在 0-199 的所有组合上进行了测试,这是 800 万次测试(我知道有点过头了),并且它有效。【参考方案11】:用 a,b,p 从 0 到 10000 进行测试:
a != 1 && a != (b-p-1) && a <= (b-p);
我认为它可以进一步简化。
【讨论】:
【参考方案12】:这很简单。
def calc(a, b, p):
if (a != 1):
temp = a - b + p
if temp == 0 or temp < -1:
return True
return False
也可以写成:
def calc(a, b, p):
temp = a - b + p
return a != 1 and (temp == 0 or temp < -1)
或如:
def calc(a, b, p):
temp = a - b + p
return a != 1 and temp <= 0 and temp != -1
【讨论】:
【参考方案13】:// In one line:
return (a != 1) && ((b-a-p == 0) || (b-a-p > 1))
// Expanded for the compiler:
if(a == 1)
return false;
int bap = b - a - p;
return (bap == 0) || (bap > 1);
如果您发布您正在使用的处理器,我可以针对组装进行优化。 =]
【讨论】:
【参考方案14】:jjngy 说得对。这是一个证明,他的简化公式等同于使用Coq Proof Assistant 的原始公式。
Require Import Arith.
Require Import Omega.
Lemma eq : forall (a b p:nat),
(((a+p) <= b) /\ ((a = 0) \/ (a > 1)) /\ (b >= p)) /\
((b - (a + p) = 0) \/ (b - (a + p) > 1)) <->
((a + p <= b) /\ ~ (a= 1) /\ ~ (b - a - p = 1)).
Proof. intros; omega. Qed.
【讨论】:
【参考方案15】:对于原始推导中的错误,我深表歉意。这就是重构后不去单元测试时会发生的情况!
以下是更正后的推导,以测试程序的形式。
简短的回答是:
((a > 1) && (skeet == 0)) || ((a > 1) && (jon > 0) && (skeet < -1));
在哪里
jon = (b - p)
skeet = (a - jon);
class Program
static void Main(string[] args)
bool ok = true;
for (int a = 1; a < 100; a++)
Console.Write(a.ToString());
Console.Write("...");
for (int b = 1; b < 100; b++)
for (int p = 1; p < 100; p++)
bool[] results = testFunctions(a, b, p);
if (!allSame(results))
Console.WriteLine(string.Format(
"Fails for 0,1,2", a, b, p));
for (int i = 1; i <= results.Length; i++)
Console.WriteLine(i.ToString() + ": " +
results[i-1].ToString());
ok = false;
break;
if (!ok) break;
if (!ok) break;
if (ok) Console.WriteLine("Success");
else Console.WriteLine("Failed!");
Console.ReadKey();
public static bool allSame(bool[] vals)
bool firstValue = vals[0];
for (int i = 1; i < vals.Length; i++)
if (vals[i] != firstValue)
return false;
return true;
public static bool[] testFunctions(int a, int b, int p)
bool [] results = new bool[16];
//given: all values are positive integers
if (a<=0 || b<=0 || p<=0)
throw new Exception("All inputs must be positive integers!");
//[1] original expression
results[0] = (((a+p) <= b) && (a == 0 || a > 1) && (b >= p)) &&
((b - (a + p) == 0) || (b - (a + p) > 1));
//[2] a==0 cannot be true since a is a positive integer
results[1] = (((a+p) <= b) && (a > 1) && (b >= p)) &&
((b - (a + p) == 0) || (b - (a + p) > 1));
//[3] rewrite (b >= p) && ((a+p) <= b)
results[2] = (b >= p) && (b >= (a+p)) && (a > 1) &&
((b - (a + p) == 0) || (b - (a + p) > 1));
//[4] since a is positive, (b>=p) guarantees (b>=(p+a)) so we
//can drop the latter term
results[3] = (b >= p) && (a > 1) &&
((b - (a + p) == 0) || (b - (a + p) > 1));
//[5] separate the two cases b>=p and b=p
results[4] = ((b==p) && (a > 1) && ((b - (a + p) == 0) ||
(b - (a + p) > 1))) || ((b > p) && (a > 1) &&
((b - (a + p) == 0) || (b - (a + p) > 1)));
//[6] rewrite the first case to eliminate p (since b=p
//in that case)
results[5] = ((b==p) && (a > 1) && ((-a == 0) ||
(-a > 1))) || ((b > p) && (a > 1) &&
(((b - a - p) == 0) || ((b - a - p) > 1)));
//[7] since a>0, neither (-a=0) nor (-a>1) can be true,
//so the case when b=p is always false
results[6] = (b > p) && (a > 1) && (((b - a - p) == 0) ||
((b - a - p) > 1));
//[8] rewrite (b>p) as ((b-p)>0) and reorder the subtractions
results[7] = ((b - p) > 0) && (a > 1) && (((b - p - a) == 0) ||
((b - p - a) > 1));
//[9] define (b - p) as N temporarily
int N = (b - p);
results[8] = (N > 0) && (a > 1) && (((N - a) == 0) || ((N - a) > 1));
//[10] rewrite the disjunction to isolate a
results[9] = (N > 0) && (a > 1) && ((a == N) || (a < (N - 1)));
//[11] expand the disjunction
results[10] = ((N > 0) && (a > 1) && (a == N)) ||
((N > 0) && (a > 1) && (a < (N - 1)));
//[12] since (a = N) in the first subexpression we can simplify to
results[11] = ((a == N) && (a > 1)) ||
((N > 0) && (a > 1) && (a < (N - 1)));
//[13] extract common term (a > 1) and replace N with (b - p)
results[12] = (a > 1) && ((a == (b - p)) ||
(((b - p) > 0) && (a < (b - p - 1))));
//[14] extract common term (a > 1) and replace N with (b - p)
results[13] = (a > 1) && (((a - b + p) == 0) ||
(((b - p) > 0) && ((a - b + p) < -1)));
//[15] replace redundant subterms with intermediate
//variables (to make Jon Skeet happy)
int jon = (b - p);
int skeet = (a - jon); //(a - b + p) = (a - (b - p))
results[14] = (a > 1) && ((skeet == 0) ||
((jon > 0) && (skeet < -1)));
//[16] rewrite in disjunctive normal form
results[15] = ((a > 1) && (skeet == 0)) ||
((a > 1) && (jon > 0) && (skeet < -1));
return results;
【讨论】:
@Steven: (a == 0 || a > 1) 不能简化为 (a > 1)。它是 (a != 1)。 @wasker - 如果我们假设 0 既不是负数也不是正数,那么 (a > 1) 和 (a != 1) 具有相同的真值表。 你说 "(a == 0 || a > 1) 减少到 (a > 1)" 和 "N == 0 || N > 1 意味着 N >= 0 对于正整数",但两者都不正确。假设 a >=0(我们被告知), (a==0 || a > 1) 减少到 a != 1,正如许多其他发帖人所指出的那样。 Stephen A. Lowe 是正确的。最初的问题表明整数是正数。因此,零不是一个允许的值。因此,(a==0 || a>1) => (a > 1)。这是初级的! 我真的不敢相信这么多人错过了这个。我猜那些疯狂的 IEEE 浮点数让人们对数字的工作原理感到困惑!【参考方案16】:嗯
((b - (a + p) == 0) || (b - (a + p) > 1))
Would be better writen as:
(b - (a + p) >= 0)
Applying this to the whole string you get:
((a+p) <= b) && (a > 1) && (b >= p)) && (b - (a + p) >= 0)
(a + p) <= b is the same thing as b - (a + p) >= 0
所以你可以摆脱这种离开:
((a+p) <= b) && (a > 1) && (b >= p))
【讨论】:
不! ((b - (a + p) == 0) || (b - (a + p) > 1)) 不等于 (b - (a + p) >= 0) !!!正确的缩减是:(b - (a + p) != 1) a can == 0 so a > 1 woudln't 足够【参考方案17】:我将此作为评论添加到 nickf 的答案中,但我认为我会自己提供它作为答案。好的答案似乎都是他的变体,包括我的。但是由于我们不依赖于编译器进行优化(如果 OP 是,我们甚至不会这样做)然后将其从 3 个 AND 归结为以下意味着将有 3 个部分中只有 2 个的值将需要评估。如果这是在脚本中完成的,与编译后的代码相比,它会有所不同。
(a != 1) && ((b > (a + p + 1)) || (b == (a + p))))
根据评论,我将添加这个比 AND 版本更好的内容:
我想这取决于您的真实结果数据集是否大于输入集的 50%。输入越频繁,我的变化就会越好。因此,使用这个等式,看起来 AND 样式会更好(至少对于我的输入数据集 0-500)。
【讨论】:
脚本不会有条件地处理 && 无论如何...即如果是 A && B && C,一旦其中一个为假,它将停止处理。替换 ||只是意味着如果第一个是假的,或者如果 || 之一,它将停止条款是真的。不确定这是如何改进的。 好点。我想这取决于您的真实结果数据集是否大于输入集的 50%。输入越频繁,我的变化就会越好。所以,有了这个等式,看起来 AND 样式会更好(至少对于我的输入数据集 0-500)。【参考方案18】:如果a、b和p是正整数(假设正范围包括0值)那么表达式(((a+p) 1) && (b >= p)) && ((b - (a + p) == 0) || (b - (a + p) > 1)) 可以简化为 ((a+p) && ((b-(a+p))!=1)
让我演示一下: 在表达式的第一部分有一个条件,((a+p), 如果评估为真,则第二部分为真: ((b - (a + p) == 0) || (b - (a + p) > 1))。如果 (b >=(a+p)) 是真的,那么 (b - (a+p)) 必须大于或等于 0,我们需要以确保 (b-(a+p))!=1。 把这个术语放在一边,然后继续。
现在我们可以将精力集中在第一部分 (((a+p) 1) && (b >= p)) && ((b-(a+p))! =1)
如果 a 是肯定的,那么它总是 >=0,所以我们可以放弃测试 (a == 0 || a > 1) 如果赞成 ( a!=1) 并将表达式的第一部分简化为 (((a+p) = p) && (a!=1))。
对于减少的下一步,您可以考虑如果 b >= (a+p) 那么,显然 b>=p (a 是正数),表达式可以简化为
((a+p) && ((b-(a+p))!=1)
【讨论】:
"假设正数范围包括 0 值"。我们也可以加上负一的平方根吗?因为它也是一个很酷的数字! 它不是整数;)【参考方案19】:以下逻辑如何,请评论:
((a == 0 || a > 1) && ((b-p) > 1) )
【讨论】:
【参考方案20】:好的,我希望我的数学是在这里完成的,但如果我是对的,那么这会简化很多。虽然最后看起来不一样,但核心逻辑应该是一样的。
// Initial equation
(((a + p) <= b) && (a == 0 || a > 1) && (b >= p)) && ((b - (a + p) == 0) || (b - (a + p) > 1))
// ((a + p) <= b) iif a = 0 && p = b; therefore, b = p and a = 0 for this to work
(b == p) && ((b - (a + p) == 0) || (b - (a + p) > 1))
// Simplification, assuming that b = p and a = 0
(b == p) && (a == 0)
但是,如果我们在零既不是positive or negative 的假设下运行,那么这意味着提供给等式的任何值都将大于或等于 1。这反过来意味着,由于以下事实,该等式将始终评估为假:
(a == 0 || a > 1)
只有当 a >= 2 时才会评估为真;但是,如果以下情况也成立:
(b >= p)
那么这意味着p至少等于b,因此:
((a + p) <= b)
通过替换变为:
((2 + b) <= b)
这显然永远无法评估为真。
【讨论】:
b - (a + p) >= 0)。这不是真的; b - (a + p ) 不能等于 1 ;) 以及为什么你在 "(a == 0 || a > 1)" 语句中转义了 a > 1 部分。 @yapiskan - 如果我假设 ((a + p) 1) 因为等式将允许 ((a + b) 1),这显然是错误的。【参考方案21】:(((a+p) 1) && (b >= p)) && ((b - (a + p) == 0) || (b - (a + p) > 1))
1) (a == 0 || a > 1) 是 (a != 1)
2) (b >= p) 是 (b - p >= 0)
(a + p = a),比 (b - p >= 0) 强。
第一个条件简化为 (a != 1) && (b - p >= a)。
3) (b - (a + p) == 0) 是 (b - a - p == 0) 是 (b - p == a)。
(b - (a + p) > 1) 是 (b - a - p > 1) 是 (b - p > 1 + a)。
由于我们有 (b - p >= a) 并且我们使用 && 操作,我们可以说 (b - p >= a) 涵盖 (b - p == a && b - p > 1 + a)。
因此,整个条件将简化为
(a != 1 && (b - p >= a))
有一种将它进一步减少到 (b >= p) 的诱惑,但这种减少不会包括禁止 b = p + 1,因此 (a != 1 && (b - p >= a)) 是条件。
【讨论】:
【参考方案22】:这个问题已经在实践中得到了很好的回答,但是我在下面提到的一点我还没有看到其他人提出。
由于我们被告知假设 a >= 0,并且第一个条件确保 b - (a + p) >= 0,所以括号中的 ||测试可以变成针对不等式的测试:1:
(a + p = p) && (b - a - p != 1)
删除检查 (b >= p) 是很有诱惑力的,这会给出 nickf 的表达式。这几乎可以肯定是正确的实际解决方案。不幸的是,我们需要更多地了解问题域,然后才能确定这样做是否安全。
例如,如果对 a、b 和 p 的类型使用 C 和 32 位无符号整数,请考虑 a = 2^31 + 7、p = 2^31 + 5、b = 13 的情况。我们有 a > 0,(a + p) = 12
您的值可能不会接近存在这种溢出问题的范围,但您应该检查这个假设。如果事实证明这是可能的,请添加一个带有该表达式的注释来解释这一点,这样一些热心的未来优化器就不会粗心地删除 (b >= p) 测试。
【讨论】:
a 为正整数,表示 a>0 而不是 a >= 0【参考方案23】:我觉得 (a != 1) && (a + p
int t = b-p; (a != 1 && a
a 基本上是 0、t 或介于 2 和 t-2 之间。
【讨论】:
【参考方案24】:a!=1 && ((b == a + p) || (b - p > a + 1))
【讨论】:
【参考方案25】:第一次迭代:
bool bool1 = ((a+p) <= b) && (a == 0 || a > 1) && (b >= p);
bool bool2 = (b - (a + p) == 0) || (b - (a + p) > 1);
return bool1 && bool2;
第二次迭代:
int value1 = b - (a + p);
bool bool1 = (value1 >= 0) && (a == 0 || a > 1) && (b >= p);
bool bool2 = (value1 == 0) || (value1 > 1);
return bool1 && bool2;
第三次迭代(所有正面)
int value1 = b - (a + p);
bool bool1 = (value1 >= 0) && (a != 1) && (b >= p);
bool bool2 = (value1 == 0) || (value1 > 1);
return bool1 && bool2;
第 4 次迭代(所有阳性)
int value2 = b - p;
int value1 = value2 - a;
bool bool1 = (value1 >= 0) && (a != 1) && (b - p >= 0);
bool bool2 = (value1 == 0) || (value1 > 1);
return bool1 && bool2;
第 5 次迭代:
int value2 = b - p;
int value1 = value2 - a;
bool bool1 = (value1 >= 0) && (a != 1) && (value2 >= 0);
bool bool2 = (value1 == 0) || (value1 > 1);
return bool1 && bool2;
【讨论】:
这是我见过的第一个真正有效的,但很难称之为简化...... 为谁简化?为编译器还是为人类?对于编译器,没有必要。对于人类来说,只需使用有意义的名称而不是 value1 和 value2。【参考方案26】:b >= (a+p) && a>=1
即使b >= p
也是多余的,因为a >= 1
总是如此
【讨论】:
@PEZ:不,不是。正整数从 1 开始。请参阅 mathworld.wolfram.com/PositiveInteger.html 或 wiki.answers.com/Q/What_is_a_positive_integer【参考方案27】:(((a+p) <= b) && (a == 0 || a > 1) && (b >= p)) && ((b - (a + p) == 0) || (b - (a + p) > 1))
由于 a >=0(正整数),项 (a == 0 || a > 1) 始终为真
如果 ((a+p) = p) 当 a,b,p >=0 时为真
因此 ((a+p) 1) && (b >= p)) && ((b - (a + p) == 0) 减少到
b>=(a+p)
(b - (a + p) == 0) || (b - (a + p) > 1) 等价于 b>=(a+p)
因此整个方程简化为
**b>= (a+p)**
【讨论】:
以上是关于你能简化这个算法吗?的主要内容,如果未能解决你的问题,请参考以下文章