(X == 0 || x == 1) tek bir işlemde sadeleştirmek mümkün mü?


106

Ben yazmaya çalışıyordum Yani n mümkün olduğunca kompakt bir fonksiyonu olarak Fibonacci dizisindeki inci numarayı:

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

Ancak bunu değiştirerek bunu daha kompakt ve verimli hale getirebilir miyim?

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

tek bir karşılaştırmaya. Bunu yapabilen bazı süslü bit kaydırma işlemi var mı?


111
Neden? Okunabilir, niyet çok açık ve pahalı değil. Neden anlaşılması daha zor olan ve amacı açıkça tanımlamayan "akıllı" bir bit örüntü eşlemesine değiştirilsin?
D Stanley

9
Bu gerçekten fibonaci değil, değil mi?
n8wrl

9
fibonaci önceki iki değeri ekler. Şunu mu demek istediniz fibn(N-1) + fibn(N-2) yerine N * fibn(N-1)?
juharr

46
Nanosaniyeleri azaltmaktan yanayım, ancak özyineleme kullanan bir yöntemde basit bir karşılaştırmanız varsa, neden karşılaştırmanın verimliliği için çaba sarf edip özyinelemeyi orada bırakasınız?
Jon Hanna

25
Fabonacci sayısını hesaplamak için özyinelemeli bir yol kullanıyorsun, sonra performansı artırmak mı istiyorsun? Neden onu bir döngüye dönüştürmüyorsunuz? veya hızlı güç kullan?
notbad

Yanıtlar:


-9

Bu da çalışıyor

Math.Sqrt(N) == N 

0 ve 1'in karekökü sırasıyla 0 ve 1'i döndürür.


20
Math.Sqrtkarmaşık bir kayan nokta işlevidir. Yalnızca tamsayı alternatiflerine kıyasla yavaş çalışır !!
Nayuki

1
Temiz görünüyor, ancak diğer cevapları kontrol ederseniz bundan daha iyi yollar var.
Mafii

9
Üzerinde çalıştığım herhangi bir kodda bununla karşılaşırsam, muhtemelen, en azından, o kişinin masasına yürür ve o sırada hangi maddeyi tükettiklerini açıkça sorardım.
CVn

Aklı başında kim bunu cevap olarak işaretledi? Konuşmayan.
squashed.bugaboo

212

Bitsel aritmetik kullanarak aritmetik testinizi uygulamanın birçok yolu vardır. İfadeniz:

  • x == 0 || x == 1

mantıksal olarak şunların her birine eşdeğerdir:

  • (x & 1) == x
  • (x & ~1) == 0
  • (x | 1) == 1
  • (~x | 1) == (uint)-1
  • x >> 1 == 0

Bonus:

  • x * x == x (kanıt biraz çaba gerektirir)

Ancak pratik olarak konuşursak, bu formlar en okunabilir olanlardır ve performanstaki küçük fark, bit tabanlı aritmetiği kullanmaya gerçekten değmez:

  • x == 0 || x == 1
  • x <= 1(çünkü xişaretsiz bir tamsayıdır)
  • x < 2(çünkü xişaretsiz bir tamsayıdır)

6
Unutma(x & ~1) == 0
Lee Daniel Crocker

71
Ancak bunlardan herhangi birinin "daha verimli" olduğuna bahse girmeyin. gcc aslında x == 0 || x == 1for (x & ~1) == 0veya için olduğundan daha az kod üretir (x | 1) == 1. Birincisi, bir basitle eşdeğer olduğunu kabul edecek x <= 1ve çıktı verecek kadar akıllı cmpl; setbe. Diğerleri onu karıştırır ve daha kötü kod üretmesini sağlar.
hobbs

13
x <= 1 veya x <2 daha basittir.
gnasher729

9
@Kevin True for C ++, çünkü bu standart uyumlu kod yazmayı imkansız hale getirmek için gerçekten çok çalışıyor. Neyse ki bu C # ile ilgili bir soru;)
Voo

5
C # derleyicisinin ve .NET JITter'ın ne kadar akıllı olduğunu bilmesem de, çoğu modern derleyici zaten bu gibi karşılaştırmaları optimize edebilir . Gerçek kodda sadece tek bir karşılaştırma gereklidir
phuclv

78

Bağımsız değişken uint( işaretsiz ) olduğundan

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

Daha az okunabilir (IMHO) ancak her karakteri sayarsanız ( Kod Golf veya benzeri)

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

Düzenle : düzenlenmiş sorunuz için :

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

Veya

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

12
Code Golf olsaydı, olurdu return N<2?1:f(N-1)+f(n-2). : P
Conor O'Brien

36

Ayrıca diğer tüm bitlerin şu şekilde 0 olup olmadığını da kontrol edebilirsiniz:

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

Matt sayesinde eksiksizlik için daha da iyi çözüm:

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

Her iki durumda da paranteze dikkat etmeniz gerekir çünkü bitsel operatörler bundan daha düşük önceliğe sahiptir ==.


Bunu sevdim! Teşekkürler.
user6048670

15
1 karakter daha az:(N|1)==1
Mat

1
@atk 3 | 1 = 3 çünkü b0011 | b0001 = b0011
René Vogt

3
@atk Bu bitseldir veya mantıksal değildir veya. Kısa devre yoktur.
isaacg

2
@ Hoten Correct, ama Matt 1 karakter eksik, 1 operasyon eksik dedi .
Ivan Stoev

20

Yapmak istediğiniz, işlevi daha verimli hale getirmekse, bir arama tablosu kullanın. Arama tablosu sadece 47 girişle şaşırtıcı derecede küçüktür - sonraki giriş 32 bitlik işaretsiz bir tamsayıyı aşacaktır. Elbette bu işlevi yazmak için önemsiz hale getirir.

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

Açıkça aynı şeyi faktoriyeller için de yapabilirsiniz.


14

Bitshift ile nasıl yapılır

Bitshift'i kullanmak ve kodu biraz belirsiz (ancak kısa) yapmak istiyorsanız, şunları yapabilirsiniz:

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

NC dilinde işaretsiz bir tamsayı için,N>>1 için, düşük dereceli biti fırlatır. Bu sonuç sıfır değilse, N'nin 1'den büyük olduğu anlamına gelir.

Not: Bu algoritma, halihazırda hesaplanmış olan sıradaki değerleri gereksiz yere yeniden hesapladığı için korkunç derecede verimsizdir.

ÇOK HIZLI bir şey

Örtülü olarak fibonaci (N) büyüklüğünde bir ağaç oluşturmak yerine bunu bir geçişte hesaplayı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;
}

Bazılarının da bahsettiği gibi, 64 bitlik işaretsiz bir tamsayının bile taşması uzun sürmez. Ne kadar büyük olmaya çalıştığınıza bağlı olarak, keyfi hassas tamsayılar kullanmanız gerekecek.


1
Sadece üstel büyüyen ağaçtan kaçınmakla kalmaz, aynı zamanda modern CPU boru hatlarını tıkayabilecek üçlü operatörün potansiyel dallanmasını da önlersiniz.
mathreadler

2
'Çok daha hızlı' kodunuz C #'da çalışmaz çünkü uintdolaylı olarak atanabilir değildir boolve soru özellikle C # olarak etiketlenir.
Pharap

1
@pharap sonra yapın --N != 0. Buradaki nokta, O (n) bir şeyin O (fibn (n)) ye tercih edilmesidir.
Matthew Gunn

1
@ MatthewGunn'un noktasını genişletmek için, O (fib (n)) O (phi ^ n) 'dir (bu türetme stackoverflow.com/a/360773/2788187'ye bakın )
Connor Clark

@ RenéVogt Ben ac # geliştiricisi değilim. Çoğunlukla bir O (fibn (N)) algoritmasının tam anlamsızlığı üzerine yorum yapmaya çalışıyordum. Şimdi derleniyor mu? (C # sıfır olmayan sonuçları doğru olarak değerlendirmediği için! = 0 ekledim.) Uint'i uint64_t gibi standart bir şeyle değiştirirseniz düz c'de çalışır (ve çalışır).
Matthew Gunn

10

Negatif olamayan bir uint kullandığınızda, aşağıdakileri kontrol edebilirsiniz: n < 2

DÜZENLE

Veya bu özel işlev durumu için aşağıdaki gibi yazabilirsiniz:

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

Bu, elbette ek bir özyineleme adımı pahasına aynı sonuca götürür.


4
@CatthalMF: ama sonuç aynı, çünkü1 * fibn(0) = 1 * 1 = 1
derpirscher

3
İşleviniz fibonacci değil faktör hesaplaması değil mi?
Barmar

2
@Barmar evet, aslında bu faktöryel, çünkü asıl soru
buydu

3
O fibnzaman
aramamak

1
@ pie3636 buna fibn adını verdim çünkü orijinal soru bu şekilde çağrıldı ve cevabı daha sonra güncellemedim
derpirscher

6

NN'nin işaretsiz olduğunu bildiğiniz için <= 1 olup olmadığını kontrol edin : 0 ve 1 ile N <= 1sonuçlanan yalnızca 2 koşul olabilirTRUE

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

İmzalı ya da imzasız olması fark eder mi? Algoritma, negatif girdilerle sonsuz özyineleme üretir, bu nedenle onları 0 veya 1'e eşdeğer olarak ele
almanın bir

@Barmar eminim, özellikle bu özel durumda önemli. OP tam olarak basitleştirip basitleştiremeyeceğini sordu (N == 0 || N == 1). 0'dan küçük olmayacağını biliyorsunuz (çünkü o zaman imzalanacaktı!) Ve maksimum 1 olabilir N <= 1. Bunu basitleştirir. Sanırım işaretsiz tip garanti edilmiyor, ancak bunun başka bir yerde ele alınması gerektiğini söyleyebilirim.
james

int NDemek istediğim, eğer ilan edilmiş olsaydı ve orijinal koşulu koruduysanız, N orijinal haliyle negatif olduğunda sonsuza kadar tekrar ederdi. Bu tanımlanmamış bir davranış olduğundan, aslında bunun için endişelenmemize gerek yok. Bu nedenle, beyana bakılmaksızın N'nin negatif olmadığını varsayabiliriz.
Barmar

Veya negatif girdilerle istediğimiz her şeyi yapabiliriz, bunlara özyinelemenin temel durumu olarak davranmak dahil.
Barmar

@Barmar, negatif olarak ayarlamaya çalışırsanız uint'in her zaman işaretsiz olarak dönüştürüleceğinden oldukça emin
james

6

Sorumluluk reddi: C # bilmiyorum ve şu kodu test etmedim:

Ama merak ediyorum, [...] 'yi tek bir karşılaştırmaya çevirerek bunu daha derli toplu ve verimli hale getirebilir miyim ...

Bit kaydırmaya ya da benzerine gerek yok, bu sadece bir karşılaştırma kullanıyor ve çok daha verimli olmalı (O (n) - O (2 ^ n) sanırım?). İşlevin gövdesi daha kompakttır , bildirimle biraz daha uzun olmasına rağmen .

(Özyinelemeden ek yükü kaldırmak için, Mathew Gunn'ın cevabında olduğu gibi yinelemeli sürüm var )

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

Not: Bu, akümülatörler ile yineleme için yaygın bir işlevsel modeldir. İle değiştirirseniz N--, N-1etkili bir şekilde mutasyon kullanmazsınız, bu da onu tamamen işlevsel bir yaklaşımla kullanılabilir kılar.


4

İşte benim çözümüm, bu basit işlevi optimize etmede pek bir şey yok, diğer yandan burada önerdiğim şey, yinelemeli işlevin matematiksel bir tanımı olarak okunabilirlik.

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

        case  1: return 1;

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

Fibonacci sayısının benzer şekilde matematiksel tanımı.

görüntü açıklamasını buraya girin

Anahtar durumunu bir arama tablosu oluşturmaya zorlamak için daha da ileri götürür.

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
Çözümünüzün avantajı, yalnızca ihtiyaç duyulduğunda hesaplanabilmesidir. En iyisi bir arama tablosu olur. alternatif bonus: f (n-1) = someCalcOf (f (n-2)), bu nedenle tamamen yeniden çalıştırmaya gerek yoktur.
Karsten

@Karsten Anahtarın bir arama tablosu oluşturmasına yetecek kadar değer ekledim. Alternatif bonusun nasıl çalıştığından emin değilim.
Halid.K

1
Bu soruya nasıl cevap veriyor?
Clark Kent

@SaviourSelf, bir arama tablosuna geliyor ve cevapta açıklanan görsel yönü var. stackoverflow.com/a/395965/2128327
Khaled.K

switchBir dizi yanıtınız varken neden a kullanasınız ?
Nayuki


1

Dmitry'ın cevabı en iyisidir, ancak bu bir Int32 dönüş türü olsaydı ve aralarından seçim yapabileceğiniz daha büyük bir tam sayı kümeniz varsa bunu yapabilirsiniz.

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

2
Orijinalinden ne kadar kısa?
MCMastery

2
@MCMastery Daha kısa değil. Bahsettiğim gibi, orijinal dönüş türünün bir int32 olması ve geniş bir geçerli sayı kümesinden seçim yapması daha iyidir. (N == -1 || N == 0 || N == 1 || N == 2)
CathalMF 2'16

1
OP'nin nedenleri optimizasyonla ilgili görünüyor. Bu, birkaç nedenden ötürü kötü bir fikirdir: 1) her özyinelemeli çağrının içinde yeni bir nesnenin somutlaştırılması gerçekten kötü bir fikirdir, 2) List.ContainsO (n) 'dur, 3) bunun yerine basitçe iki karşılaştırma yapmak ( N > -3 && N < 3) daha kısa ve daha okunabilir kod verecektir.
Groo

@Groo Peki ya değerler -10, -2, 5, 7, 13
olsaydı

OP'nin sorduğu bu değil. Ama yine de, 1) her çağrıda yeni bir örnek oluşturmak istemezsiniz, 2) bunun yerine bir (tek) hashset kullanırsanız, 3) belirli bir problem için hash fonksiyonunu optimize edebilirsiniz. saf olun, hatta diğer yanıtlarda önerildiği gibi akıllıca düzenlenmiş bitsel işlemleri kullanın.
Groo

0

Fibonacci dizisi, bir sayının kendisinden önceki iki sayıyı toplayarak bulunduğu bir sayı dizisidir. İki tür başlangıç ​​noktası vardır: ( 0,1 , 1,2, ..) ve ( 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
-----------------------------------------

Pozisyon Nbu durumda başlar 1değil mi,0-based bir dizi indeksi olarak .

Kullanımı C # 6 İfadesi vücut özelliğini ve yaklaşık Dmitry önerisi üçlü operatörü biz tip 1 için doğru hesaplama ile tek satır fonksiyon yazabiliriz:

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

ve tip 2 için:

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

-2

Partiye biraz geç ama sen de yapabilirsin (x==!!x)

!!xa değerini 1değilse değerine dönüştürür ve öyleyse olarak 0bırakır 0.
Bu tür bir şeyi C gizlemede çok kullanıyorum.

Not: Bu C, C # ile çalışıp çalışmadığından emin değilim


4
Bunun neden oy verildiğinden emin değilim. Bu denemeden bile üstünkörü olarak uint n = 1; if (n == !!n) { }verir Operator '!' cannot be applied to operand of type 'uint'üzerindeki !nC #. Bir şeyin C'de çalışması, C # dilinde çalıştığı anlamına gelmez; #include <stdio.h>C # 'da "include" önişlemci yönergesi olmadığı için bile çalışmaz. Diller C ve C ++ 'dan daha farklıdır.
Bir CVn

2
Oh. Tamam. Üzgünüm :(
One Normal Night

@OneNormalNight (x == !! x) Bu nasıl çalışacak. Girişimin 5 olduğunu düşünün (5 == !! 5). Doğru sonucu verecek
VINOTH ENERGETIC

1
@VinothKumar !! 5, 1 olarak değerlendirilir (5 == !! 5), yanlış olarak değerlendirilen (5 == 1) olarak değerlendirilir.
Bir Normal Gece

@OneNormalNight evet şimdi anladım. ! (5) 1 verir tekrar
uygular

-3

Ben Listde bu özel tam sayılardan bir tane yarattım ve Nbununla ilgili olup olmadığını kontrol ettim.

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

ContainsYalnızca bir kez çağrılan farklı amaçlar için bir uzantı yöntemi de kullanabilirsiniz (örneğin, uygulamanız başlatılırken ve veri yüklerken). Bu, daha net bir stil sağlar ve değerinizle ( N) birincil ilişkiyi netleştirir :

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

Uygula:

N.PertainsTo(ints)

Bunu yapmanın en hızlı yolu bu olmayabilir ama bana göre daha iyi bir tarz gibi görünüyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.