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

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是无符号整数)


因为参数是uint(无符号),所以您可以

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语言中的无符号整数NN>>1会丢弃低位。如果结果为非零,则意味着n大于1。

注意:该算法效率极低,因为它不必要地按照已经计算的序列重新计算值。

更快的方法

计算一次,而不是隐式构建一个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,它不能得到否定,你可以检查是否n < 2

编辑

或者对于这个特殊的函数案例,您可以编写如下:

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:这是使用累加器进行迭代的常见功能模式。如果你用N-1替换N--,你实际上没有使用任何突变,这使得它可以用于纯功能的方法。


只需检查N是否<=1,因为您知道n是无符号的,所以只有2个条件可以使N <= 1产生TRUE:0和1

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

类似的斐波那契数的数学定义。

enter image description here

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

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
return new List<int>() { -1, 0, 1, 2 }.Contains(N) ? 1 : N * fibn(N-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
-----------------------------------------

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

使用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);

参加聚会有点晚了,但你也可以参加.

如果不是0!!x将A值转换为1,如果不是0,则保留在0。我经常用这个来混淆。

注意:这是C,不确定它是否在C中工作#


所以我创建了这些特殊整数的List,并检查N是否与它相关。

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

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

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。