Is it possible to simplify (x == 0 || x == 1) into a single operation?
所以我试图用尽可能紧凑的函数来写斐波那契序列中的第n个数:
1 2 3 4 | public uint fibn ( uint N ) { return (N == 0 || N == 1) ? 1 : fibn(N-1) + fibn(N-2); } |
但是我想知道我是否可以通过改变使这个更加紧凑和高效
1 | (N == 0 || N == 1) |
进行一次比较。是否有一些奇特的位移位操作可以做到这一点?
有许多方法可以使用位算术实现算术测试。你的表情:
x == 0 || x == 1
逻辑上等同于以下每一项:
(x & 1) == x (x & ~1) == 0 (x | 1) == 1 (~x | 1) == (uint)-1 x >> 1 == 0
奖金:
x * x == x (证明需要一些努力)
但实际上,这些形式是最易读的,性能上的微小差异不值得使用位算术:
x == 0 || x == 1 x <= 1 (因为x 是无符号整数)x < 2 (因为x 是无符号整数)
因为参数是
1 | return (N <= 1) ? 1 : N * fibn(N-1); |
可读性较低(imho),但如果计算每个字符(code golf或类似字符)
1 | return N < 2 ? 1 : N * fibn(N-1); |
编辑:对于您编辑的问题:
1 | return (N <= 1) ? 1 : fibn(N-1) + fibn(N-2); |
或
1 | return N < 2 ? 1 : fibn(N-1) + fibn(N-2); |
您还可以检查所有其他位是否为0,如下所示:
1 | return (N & ~1) == 0 ? 1 : N * fibn(N-1); |
为了实现完整性,感谢Matt更好的解决方案:
1 | return (N | 1) == 1 ? 1 : N * fibn(N-1); |
在这两种情况下,都需要注意括号,因为按位运算符的优先级低于
如果您要做的是提高函数的效率,那么使用查阅表格。查找表非常小,只有47个条目——下一个条目将溢出32位无符号整数。当然,它也使得函数的编写变得简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Sequences { // Store the complete list of values that will fit in a 32-bit unsigned integer without overflow. private static readonly uint[] FibonacciSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073 }; public uint fibn(uint N) { return FibonacciSequence[N]; } } |
显然,对于阶乘也可以这样做。
如何使用bitshift
如果要使用bitshift并使代码稍微模糊(但简短),可以执行以下操作:
1 2 3 | public uint fibn ( uint N ) { return N >> 1 != 0? fibn(N-1) + finb(N-2): 1; } |
对于C语言中的无符号整数
注意:该算法效率极低,因为它不必要地按照已经计算的序列重新计算值。
更快的方法计算一次,而不是隐式构建一个fibonaci(n)大小的树:
1 2 3 4 5 6 7 8 9 | uint faster_fibn(uint N) { //requires N > 1 to work uint a = 1, b = 1, c = 1; while(--N != 0) { c = b + a; a = b; b = c; } return c; } |
正如一些人提到的,溢出一个64位无符号整数不需要很长时间。根据您试图达到的大小,您需要使用任意精度的整数。
当你使用一个uint,它不能得到否定,你可以检查是否
编辑
或者对于这个特殊的函数案例,您可以编写如下:
1 2 3 | public uint fibn(uint N) return (N == 0) ? 1 : N * fibn(N-1); } |
当然,这将导致相同的结果,代价是增加一个递归步骤。
免责声明:我不知道C,并且没有测试此代码:
But I'm wondering if I can make this even more compact and efficient by changing [...] into a single comparison...
不需要移位之类的,这只需要一个比较,而且应该更有效(我认为是O(N)还是O(2^N?).函数体更紧凑,但声明结束时要长一点。
(为了消除递归中的开销,有迭代版本,如Mathew Gunn的答案所示)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public uint fibn ( uint N, uint B=1, uint A=0 ) { return N == 0 ? A : fibn( N--, A+B, B ); } fibn( 5 ) = fibn( 5, 1, 0 ) = return 5 == 0 ? 0 : fibn( 5--, 0+1, 1 ) = fibn( 4, 1, 1 ) = return 4 == 0 ? 1 : fibn( 4--, 1+1, 1 ) = fibn( 3, 2, 1 ) = return 3 == 0 ? 1 : fibn( 3--, 1+2, 2 ) = fibn( 2, 3, 2 ) = return 2 == 0 ? 2 : fibn( 2--, 2+3, 3 ) = fibn( 1, 5, 3 ) = return 1 == 0 ? 3 : fibn( 1--, 3+5, 5 ) = fibn( 0, 8, 5 ) = return 0 == 0 ? 5 : fibn( 0--, 5+8, 8 ) = 5 fibn(5)=5 |
PS:这是使用累加器进行迭代的常见功能模式。如果你用
只需检查
1 2 3 4 | public uint fibn ( uint N ) { return (N <= 1) ? 1 : fibn(N-1) + finb(N-2); } |
这是我的解决方案,在优化这个简单函数方面没有太多,另一方面,我在这里提供的是作为递归函数的数学定义的可读性。
1 2 3 4 5 6 7 8 9 10 11 | public uint fibn(uint N) { switch(N) { case 0: return 1; case 1: return 1; default: return fibn(N-1) + fibn(N-2); } } |
类似的斐波那契数的数学定义。
进一步强制开关案例构建查找表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | public uint fibn(uint N) { switch(N) { case 0: return 1; case 1: return 1; case 2: return 2; case 3: return 3; case 4: return 5; case 5: return 8; case 6: return 13; case 7: return 21; case 8: return 34; case 9: return 55; case 10: return 89; case 11: return 144; case 12: return 233; case 13: return 377; case 14: return 610; case 15: return 987; case 16: return 1597; case 17: return 2584; case 18: return 4181; case 19: return 6765; case 20: return 10946; case 21: return 17711; case 22: return 28657; case 23: return 46368; case 24: return 75025; case 25: return 121393; case 26: return 196418; case 27: return 317811; case 28: return 514229; case 29: return 832040; case 30: return 1346269; case 31: return 2178309; case 32: return 3524578; case 33: return 5702887; case 34: return 9227465; case 35: return 14930352; case 36: return 24157817; case 37: return 39088169; case 38: return 63245986; case 39: return 102334155; case 40: return 165580141; case 41: return 267914296; case 42: return 433494437; case 43: return 701408733; case 44: return 1134903170; case 45: return 1836311903; case 46: return 2971215073; default: return fibn(N-1) + fibn(N-2); } } |
因为n是uint,只需使用
1 | N <= 1 |
Dmitry的答案是最好的,但是如果它是Int32返回类型,并且您有一组更大的整数可供选择,那么您可以这样做。
1 |
斐波那契数列是一系列的数字,通过把前面的两个数字相加就可以得到一个数字。有两种类型的起点:(0、1、1、2、…)和(1、1、2、3)。
1 2 3 4 5 6 7 8 9 10 11 | ----------------------------------------- Position(N)| Value type 1 | Value type 2 ----------------------------------------- 1 | 0 | 1 2 | 1 | 1 3 | 1 | 2 4 | 2 | 3 5 | 3 | 5 6 | 5 | 8 7 | 8 | 13 ----------------------------------------- |
在这种情况下,位置
使用c 6表达式主体特性和dmitry关于三元运算符的建议,我们可以为类型1编写一个计算正确的单行函数:
1 | public uint fibn(uint N) => N<3? N-1: fibn(N-1)+fibn(N-2); |
对于2型:
1 | public uint fibn(uint N) => N<3? 1: fibn(N-1)+fibn(N-2); |
参加聚会有点晚了,但你也可以参加.
如果不是
注意:这是C,不确定它是否在C中工作#
所以我创建了这些特殊整数的
1 2 3 4 5 6 | static List<uint> ints = new List<uint> { 0, 1 }; public uint fibn(uint N) { return ints.Contains(N) ? 1 : fibn(N-1) + fibn(N-2); } |
您还可以将扩展方法用于不同的目的,其中只调用一次
1 2 3 4 5 6 7 | static class ObjectHelper { public static bool PertainsTo<T>(this T obj, IEnumerable<T> enumerable) { return (enumerable is List<T> ? (List<T>) enumerable : enumerable.ToList()).Contains(obj); } } |
应用:
1 | N.PertainsTo(ints) |
这可能不是最快的方法,但对我来说,这似乎是一个更好的风格。
这个也行
1 | Math.Sqrt(N) == N |
0和1的平方根将分别返回0和1。