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


106

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

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

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

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

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


111
どうして?それは読みやすく、意図は非常に明確で、高価ではありません。なぜそれを理解するのが難しく、意図を明確に識別できない「賢い」ビットパターンマッチングに変更するのですか?
Dスタンリー

9
これは本当にフィボナッチじゃないの?
n8wrl

9
fibonaciは、前の2つの値を加算します。もしかしてfibn(N-1) + fibn(N-2) 代わりにN * fibn(N-1)
juharr

46
私はすべてナノセカンドを削っていますが、再帰を使用するメソッドで単純な比較がある場合、比較の効率性に努力を払い、再帰をそこに残すのはなぜですか?
Jon Hanna

25
再帰的な方法でファボナッチ数を計算し、パフォーマンスを改善したいですか?ループに変えてみませんか?または高速電力を使用しますか?
notbad

回答:


-9

これも効く

Math.Sqrt(N) == N 

0と1の平方根は、それぞれ0と1を返します。


20
Math.Sqrtは複雑な浮動小数点関数です。整数のみの代替と比較して実行が遅い!!
Nayuki、2016年

1
これは見た目はきれいですが、他の回答を確認するとこれよりも良い方法があります。
マフィイ2016年

9
私が取り組んでいるコードでこれに遭遇した場合、少なくとも、少なくともその人の机に近づき、当時どのような物質を消費していたかを指摘するでしょう。
CVn 2017

これを正解としてマークした人は誰ですか?無言。
squashed.bugaboo 2018年

212

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

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

6
忘れないでください(x & ~1) == 0
リーダニエルクロッカー

71
しかし、それらのうちのどれかが「より効率的」であることに賭けないでください。gccは実際にx == 0 || x == 1は、(x & ~1) == 0またはよりも少ないコードを生成します(x | 1) == 1。最初のものは、それがと同等であるx <= 1と認識し、単純なを出力するのに十分スマートcmpl; setbeです。他の人たちはそれを混乱させ、より悪いコードを生成させます。
ホッブズ

13
x <= 1またはx <2の方が簡単です。
gnasher729 2016

9
@Kevin True for C ++は、その標準が準拠コードを作成することを不可能にすることを非常に困難にしようとしているためです。幸い、これはC#に関する質問です;)
Voo

5
最近のほとんどのコンパイラーは、このような比較をすでに最適化できますが、C#コンパイラーと.NET JITterがどの程度優れているかはわかりません。実際のコードで必要な比較は1つだけです
phuclv

78

引数はuintunsignedなので、

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

読みにくい(IMHO)が、各文字を数える場合(コードゴルフなど)

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

12
それがコードゴルフなら、それはだろうreturn N<2?1:f(N-1)+f(n-2)。:P
Conor O'Brien

36

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

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

マットのおかげで完全を期すために、さらに優れたソリューション:

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

どちらの場合も、ビット単位の演算子の優先度はよりも低いため、かっこに注意する必要があります==


私はそれが好きです!ありがとう。
user6048670

15
1少ない文字:(N|1)==1
マット

1
B0001はb0011です| @atk 3 | b0011ので、1が3である
ルネ・フォークト

3
@atkこれはビット単位のORであり、論理ORではありません。短絡はありません。
isaacg

2
@Hoten正解ですが、マットは操作を 1つ減らすのではなく、キャラクターを 1つ減らすと述べました。
Ivan Stoev

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

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


14

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

ビットシフトを使用してコードをややあいまいに(ただし短く)したい場合は、次のようにします。

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

N言語cの符号なし整数の場合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ビットの符号なし整数であってもオーバーフローするのに時間がかかりません。移動しようとしている大きさに応じて、任意の精度の整数を使用する必要があります。


1
指数関数的に成長するツリーを回避するだけでなく、最新のCPUパイプラインを詰まらせる可能性がある3項演算子の潜在的な分岐も回避します。
mathreadler 2016

2
uint暗黙的ににキャスト可能boolではないため、「高速化」コードはC#では機能せず、質問は特にC#としてタグ付けされます。
Pharap

1
@pharapは--N != 0代わりに行います。ポイントは、O(n)がO(fibn(n))よりも好ましいということです。
Matthew Gunn、

1
MatthewGunnのポイント@に展開する、O(FIB(n)は)O(PHI ^ n)は(この導出ご覧あるstackoverflow.com/a/360773/2788187を
コナークラーク

@RenéVogt私はC#開発者ではありません。私は主にO(fibn(N))アルゴリズムの完全な不条理についてコメントしようとしていました。今すぐコンパイルできますか?(c#はゼロ以外の結果をtrueとして扱わないため、!= 0を追加しました。)uintをuint64_tなどの標準に置き換えれば、ストレートcで機能します(そして機能しました)。
Matthew Gunn

10

負にならないuintを使用しているので、 n < 2

編集

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

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

もちろん、同じ結果が得られますが、追加の再帰ステップが必要になります。


4
@CatthalMF:しかし、結果は同じです1 * fibn(0) = 1 * 1 = 1
derpirscher

3
フィボナッチではなく、階乗を計算する関数ではありませんか?
Barmar

2
@Barmarはい、確かにそれは階乗です。なぜなら、それが元の質問だったからです
derpirscher

3
そのときfibnは呼び出さない方がいいかもしれません
pie3636

1
@ pie3636私はそれをfibnと呼んだのは、元の質問でそれが呼び出された方法であり、後で答えを更新しなかったためです
derpirscher

6

かどうかを確認するために、単純にチェックしN、あなたがNのみという2つの条件があることができ、符号なしである知っているので、<= 1でN <= 1のその結果TRUE、0と1:

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

署名されているか、署名されていないかは問題ですか?このアルゴリズムは、負の入力で無限再帰を生成するので、0または1に相当それらの治療にも害はありません
Barmar

@Barmarは、特にこの特定のケースでは、それが重要であることを確認してください。OPは、正確に簡略化できるかどうか尋ねました(N == 0 || N == 1)。あなたはそれが0より小さくないことを知っています(それでそれからそれは署名されるでしょう!)、そして最大は1であるかもしれません。 N <= 1それを簡略化します。符号なしの型は保証されていないと思いますが、それは別の場所で処理する必要があると思います。
ジェームズ

私のポイントは、それが宣言int Nされ、元の状態を維持した場合、Nが元の状態で負の場合、無限に再帰することです。これは未定義の動作なので、実際にそれを心配する必要はありません。したがって、宣言に関係なく、Nは負ではないと見なすことができます。
Barmar

または、再帰のベースケースとして扱うなど、否定的な入力を使用して必要なことをすべて実行できます。
Barmar

@Barmarは、負に設定しようとすると、常にuintが符号なしに変換されることを確信しています
james

6

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

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

ビットシフトなどは必要ありません。これは比較を1つだけ使用し、はるかに効率的です(O(n)対O(2 ^ n)だと思いますか?)。関数の本体はよりコンパクトですが、宣言により少し長くなっています。

(再帰からオーバーヘッドを取り除くために、マシューガンの答えのように、反復バージョンがあります

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効果的にミューテーションを使用していないため、純粋な機能的アプローチで使用できます。


4

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

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

        case  1: return 1;

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

同様の方法でフィボナッチ数の数学的定義。

ここに画像の説明を入力してください

さらに切り替えて、検索テーブルを構築するように強制します。

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

1
ソリューションの利点は、必要なときにのみ計算されることです。ルックアップテーブルが最適です。代替ボーナス:f(n-1)= someCalcOf(f(n-2))なので、完全な再実行は必要ありません。
Karsten

@Karstenスイッチにルックアップテーブルを作成するのに十分な値をスイッチに追加しました。代替ボーナスがどのように機能するかはわかりません。
Khaled.K 2016

1
これはどのように質問に答えますか?
クラークケント

@SaviourSelfそれはルックアップテーブルに帰着し、答えで説明される視覚的な側面があります。stackoverflow.com/a/395965/2128327
Khaled.K 2016

switchさまざまな答えが得られるのに、なぜを使用するのですか
Nayuki、2016年


1

Dmitryの答えが最善ですが、それがInt32戻り型であり、より大きな整数のセットから選択する場合は、これを行うことができます。

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

2
オリジナルよりどれだけ短いのですか?
MCMastery

2
@MCMastery短くはない。元の戻り値の型がint32であり、有効な数値の大きなセットから選択している場合にのみ、その方が良いと述べました。書く必要はない(N == -1 || N == 0 || N == 1 || N == 2)
CathalMF

1
OPの理由は最適化に関係しているようです。これはいくつかの理由で悪い考えです:1)各再帰呼び出し内で新しいオブジェクトをインスタンス化することは本当に悪い考えです、2)List.ContainsO(n)、3)代わりに2つの比較を行うだけ(N > -3 && N < 3)はより短くて読みやすいコードを与えます。
Groo

@Grooそして、もし値が-10、-2、5、7、13だったらどうなるでしょう
CathalMF

それはOPが尋ねたものではありません。しかし、とにかく、あなたはまだ1)各呼び出しで新しいインスタンスを作成したくない、2)代わりに(単一の)ハッシュセットを使用するほうが良い、3)特定の問題に対して、ハッシュ関数を最適化して純粋であること、または他の回答で提案されているように巧妙に配置されたビット単位の演算を使用することさえできます。
Groo

0

フィボナッチ数列は一連の数であり、その前に2つの数を足すことで数が見つかります。開始点には、(0,1、1,2、..)と(1,1、2,3)の2つのタイプがあります。

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

Nこの場合の位置はから始まり、配列のインデックス1ではありません0-based

使用してC#6式ボディ機能を約ドミトリーの提案三項演算子は、我々はタイプ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);

-2

パーティーに少し遅れましたが、あなたも行うことができます (x==!!x)

!!xaがで1ない場合はに値を変換し、0そうである0場合はそのままにします。
私はこのようなものをCの難読化でよく使用します。

注:これはCですが、C#で機能するかどうかはわかりません


4
なぜこれが賛成されたかわからない。C#でuint n = 1; if (n == !!n) { }提供さOperator '!' cannot be applied to operand of type 'uint'れているように、これを試してみてもおかしくはありません!n。Cで機能するからといって、C#で機能するわけではありません。でも#include <stdio.h>C#「には」プリプロセッサディレクティブを持っていないため、C#での仕事をしません。言語はCおよびC ++よりも異なります。
CVn

2
ああ。はい。すみません:(
1つの通常の夜

@OneNormalNight(x == !! x)これがどのように機能するか。私の入力が5だとしましょう(5 == !! 5)。結果はtrueになります
VINOTH ENERGETIC 2016年

1
@VinothKumar !! 5は1と評価されます。(5 == !! 5)は(​​5 == 1)と評価され、falseと評価されます。
1つの通常の夜

@OneNormalNightええ、私は今それを手に入れました。!(5)は、それが0でない1を与える適用され、再び1を与える
VINOTHエネルギッシュ

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

Contains1回だけ呼び出されるさまざまな目的で拡張メソッドを使用することもできます(アプリケーションが起動してデータをロードするときなど)。これにより、より明確なスタイルが提供され、値(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)

これは最速の方法ではないかもしれませんが、私には、より良いスタイルのように見えます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.