web-dev-qa-db-ja.com

(x == 0 || x == 1)を単一の操作に単純化することは可能ですか?

だから私はフィボナッチ数列のn番目の数をできるだけコンパクトな関数で書き込もうとしていました:

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

しかし、変更することでこれをさらにコンパクトで効率的にできるかどうか疑問に思っています

(N == 0 || N == 1)

単一の比較に。これを行うことができるいくつかの派手なビットシフト操作はありますか?

106
user6048670

ビット演算を使用して算術テストを実装するには、いくつかの方法があります。あなたの表現:

  • 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 <= 1xは符号なし整数であるため)
  • x < 2xは符号なし整数であるため)
209
Nayuki

引数はuintunsigned)なので、

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

読みにくい(IMHO)が、各文字をカウントする場合(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);
78
Dmitry Bychenko

次のように、他のすべてのビットが0であることも確認できます。

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

Matt による完全なおかげで、さらに優れたソリューション:

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

どちらの場合でも、ビット演算子は==よりも優先順位が低いため、括弧を処理する必要があります。

36
René Vogt

関数をより効率的にしたい場合は、ルックアップテーブルを使用します。ルックアップテーブルは、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];
    }
}

階乗に対しても明らかに同じことができます。

20
Adam

ビットシフトでそれを行う方法

ビットシフトを使用して、コードを多少不明瞭にする(ただし短くする)場合は、次のようにします。

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

言語cの符号なし整数Nの場合、N>>1下位ビットを破棄します。その結果がゼロ以外の場合、Nが1より大きいことを意味します。

注:このアルゴリズムは、すでに計算されているシーケンスの値を不必要に再計算するため、ひどく非効率的です。

何かもっと速く

Fibonaci(N)サイズのツリーを暗黙的に構築するのではなく、1パスで計算します。

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ビットの符号なし整数でさえオーバーフローするのに長くはかからない。どれだけ大きくしようとしているかに応じて、任意の精度の整数を使用する必要があります。

14
Matthew Gunn

負にならないuintを使用すると、n < 2

編集

または、その特別な機能の場合、次のように書くことができます。

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

もちろん、追加の再帰ステップを犠牲にして同じ結果になります。

10
derpirscher

免責事項:私はC#を知らず、このコードをテストしませんでした:

しかし、[...]を1つの比較に変更することで、これをさらにコンパクトかつ効率的にできるかどうか疑問に思っています...

ビットシフトなどは必要ありません。これは比較を1回だけ使用するため、より効率的です)(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に置き換えると、効果的に突然変異を使用せず、純粋に機能的なアプローチで使用できるようになります。

6
fede s.

ここに私の解決策があります。この単純な関数を最適化することはあまりありません。一方で、ここで提供しているのは、再帰関数の数学的定義としての可読性です。

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

さらに進んで、スイッチケースにルックアップテーブルを作成させます。

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);
    }
}
4
Khaled.K

nはuintであるため、使用するだけです

N <= 1
3
yanghaogn

Dmitryの答えは最高ですが、それがInt32戻り値型で、選択する整数のセットが大きい場合は、これを行うことができます。

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

フィボナッチ数列は一連の数字であり、数字はその前にある2つの数字を合計することで見つけられます。開始点には2つのタイプがあります:(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の提案を使用すると、タイプ1の正しい計算で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);
0
Artru