是否可以将(x == 0 || x == 1)简化为单个操作?

时间:2016-04-01 15:00:09

标签: c# algorithm optimization arithmetic-expressions

所以我试图在Fibonacci序列中尽可能紧凑地编写 n 数字:

public uint fibn ( uint N ) 
{
   return (N == 0 || N == 1) ? 1 : fibn(N-1) + fibn(N-2);
}

但我想知道我是否可以通过更改

来使其更加紧凑和高效
(N == 0 || N == 1)

进入单一比较。是否有一些奇特的位移操作可以做到这一点?

15 个答案:

答案 0 :(得分:208)

有许多方法可以使用按位算法实现算术测试。你的表达:

  • 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 :(得分:78)

由于参数为uint unsigned ),您可以输入

  return (N <= 1) ? 1 : N * fibn(N-1);

可读性较低(恕我直言),但如果算上每个角色( Code Golf 等)

  return N < 2 ? 1 : N * fibn(N-1);

修改:针对已修改的问题

  return (N <= 1) ? 1 : fibn(N-1) + fibn(N-2);

  return N < 2 ? 1 : fibn(N-1) + fibn(N-2);

答案 2 :(得分:36)

您还可以检查所有其他位是否为0:

return (N & ~1) == 0 ? 1 : N * fibn(N-1);

为了完整性,感谢Matt更好的解决方案:

return (N | 1) == 1 ? 1 : N * fibn(N-1);

在这两种情况下,您都需要注意括号,因为按位运算符的优先级低于==

答案 3 :(得分:20)

如果您想要做的是提高效率,请使用查找表。只有47个条目的查找表非常小 - 下一个条目将溢出32位无符号整数。它当然也使得函数写得很简单。

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];
    }
}

显然,你可以为因子做同样的事情。

答案 4 :(得分:14)

如何使用bitshift

如果你想使用bitshift并使代码有点模糊(但很短),你可以这样做:

public uint fibn ( uint N ) {
   return N >> 1 != 0? fibn(N-1) + finb(N-2): 1;
}

对于语言c中的无符号整数NN>>1将关闭低位。如果该结果为非零,则表示N大于1。

注意:此算法非常低效,因为它会不必要地重新计算已经计算过的序列中的值。

更快的方式

计算它一次而不是隐式地构建一个大小为fibonaci(N)的树:

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位无符号整数溢出也不需要很长时间。根据你想要的大小,你需要使用任意精度整数。

答案 5 :(得分:10)

当您使用不能消极的uint时,您可以检查n < 2是否

修改

或者对于那个特殊功能案例,您可以按如下方式编写:

public uint fibn(uint N)
    return (N == 0) ? 1 : N * fibn(N-1);
}

这将导致相同的结果,当然是以额外的递归步骤为代价。

答案 6 :(得分:6)

只需检查N是否&lt; = 1,因为您知道N是无符号的,只有2个N <= 1导致TRUE的条件:0和1

public uint fibn ( uint N ) 
{
   return (N <= 1) ? 1 : fibn(N-1) + finb(N-2);
}

答案 7 :(得分:6)

免责声明:我不知道C#,也没有测试此代码:

  

但我想知道我是否可以通过将[...]改为单一比较来使其更加紧凑和高效......

不需要比特移位等,这只使用一次比较,它应该更多效率(O(n)vs O(2 ^ n)我认为?)。该函数的主体是更紧凑,虽然声明的结尾有点长。

(为了消除递归的开销,有迭代版本,如Mathew Gunn's answer

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:这是使用累加器进行迭代的常用功能模式。如果将N--替换为N-1,则有效地使用无变异,这使其可用于纯函数方法。

答案 8 :(得分:4)

这是我的解决方案,在优化这个简单的功能方面并不多,另一方面,我在这里提供的是可读性作为递归函数的数学定义。

public uint fibn(uint N) 
{
    switch(N)
    {
        case  0: return 1;

        case  1: return 1;

        default: return fibn(N-1) + fibn(N-2);
    }
}

Fibonacci数的数学定义以类似的方式..

enter image description here

进一步强制开关盒构建查找表。

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);
    }
}

答案 9 :(得分:3)

对于N是uint,只需使用

N <= 1

答案 10 :(得分:1)

Dmitry的答案是最好的,但如果它是一个Int32返回类型,你有一个更大的整数集可供选择,你可以这样做。

return new List<int>() { -1, 0, 1, 2 }.Contains(N) ? 1 : N * fibn(N-1);

答案 11 :(得分:0)

Fibonacci序列是一系列数字,通过将前面的两个数字相加得到一个数字。有两种类型的起点:( 0,1 ,1,2,..)和( 1,1 ,2,3)。

-----------------------------------------
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
-----------------------------------------

在这种情况下,位置N1开始,它不是0-based作为数组索引。

使用C# 6 Expression-body feature和Dmitry关于ternary operator的建议,我们可以编写一行函数,并为类型1正确计算:

public uint fibn(uint N) => N<3? N-1: fibn(N-1)+fibn(N-2);

和类型2:

public uint fibn(uint N) => N<3? 1: fibn(N-1)+fibn(N-2);

答案 12 :(得分:-2)

派对迟到了,但你也可以做(x==!!x)

!!x如果值1,则将其转换为0,如果值为0,则将其保留为UIStackView。 我在C语言混淆中经常使用这种东西。

注意:这是C,不确定它是否适用于C#

答案 13 :(得分:-3)

所以我创建了List这些特殊整数,并检查了N是否属于它。

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);
}

您还可以将扩展方法用于不同的目的,其中Contains仅被调用一次(例如,当您的应用程序启动并加载数据时)。这提供了更清晰的风格,并阐明了与您的价值的主要关系(N):

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);
    }
}

应用它:

N.PertainsTo(ints)

这可能不是最快的方法,但对我而言,它似乎是一种更好的风格。

答案 14 :(得分:-8)

这个也有效

Math.Sqrt(N) == N 

0和1的平方根将分别返回0和1。