Apakah mungkin untuk menyederhanakan (x == 0 || x == 1) menjadi satu operasi?

106

Jadi saya mencoba menulis angka ke- n dalam deret Fibonacci dalam fungsi sesingkat mungkin:

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

Tapi saya bertanya-tanya apakah saya bisa membuat ini lebih ringkas dan efisien dengan mengubahnya

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

menjadi satu perbandingan. Apakah ada operasi bit shift yang bagus yang dapat melakukan ini?

pengguna6048670
sumber
111
Mengapa? Dapat dibaca, maksudnya sangat jelas, dan tidak mahal. Mengapa mengubahnya menjadi beberapa pencocokan pola bit yang "pintar" yang lebih sulit dipahami dan tidak secara jelas mengidentifikasi maksudnya?
D Stanley
9
Ini bukan fibonaci kan?
n8wrl
9
fibonaci menambahkan dua nilai sebelumnya. Apakah maksud Anda fibn(N-1) + fibn(N-2) bukan N * fibn(N-1)?
juharr
46
Saya setuju untuk memangkas nanodetik, tetapi jika Anda memiliki perbandingan sederhana dalam metode yang menggunakan rekursi, mengapa menghabiskan upaya untuk efisiensi perbandingan, dan membiarkan rekursi di sana?
Jon Hanna
25
Anda menggunakan cara rekursif untuk menghitung angka Fabonacci, lalu Anda ingin meningkatkan kinerjanya? Mengapa tidak mengubahnya menjadi loop? atau gunakan tenaga cepat?
notbad

Jawaban:

-9

Yang ini juga berfungsi

Math.Sqrt(N) == N 

akar kuadrat dari 0 dan 1 akan mengembalikan 0 dan 1 masing-masing.

Rahul
sumber
20
Math.Sqrtadalah fungsi floating-point yang rumit. Ini berjalan lambat dibandingkan dengan alternatif hanya integer !!
Nayuki
1
Ini terlihat bersih, tetapi ada cara yang lebih baik dari ini jika Anda memeriksa jawaban lain.
Mafii
9
Jika saya menemukan ini dalam kode apa pun yang sedang saya kerjakan, saya kemungkinan besar, setidaknya, akan berjalan ke meja orang itu dan dengan tajam bertanya kepada mereka zat apa yang mereka konsumsi pada saat itu.
CVn
Siapa yang waras, menandai ini sebagai jawabannya? Terdiam.
squashed.bugaboo
212

Ada beberapa cara untuk menerapkan tes aritmatika Anda menggunakan aritmatika bitwise. Ekspresi Anda:

  • x == 0 || x == 1

secara logis setara dengan masing-masing berikut ini:

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

Bonus:

  • x * x == x (buktinya membutuhkan sedikit usaha)

Tetapi secara praktis, formulir ini adalah yang paling mudah dibaca, dan perbedaan kecil dalam performa tidak terlalu berguna jika menggunakan aritmatika bitwise:

  • x == 0 || x == 1
  • x <= 1(karena xadalah bilangan bulat yang tidak bertanda tangan)
  • x < 2(karena xadalah bilangan bulat yang tidak bertanda tangan)
Nayuki
sumber
6
Jangan lupa(x & ~1) == 0
Lee Daniel Crocker
71
Tapi jangan bertaruh pada salah satu dari mereka yang "lebih efisien". gcc sebenarnya menghasilkan lebih sedikit kode untuk x == 0 || x == 1daripada untuk (x & ~1) == 0atau (x | 1) == 1. Untuk yang pertama, cukup pintar untuk mengenalinya sebagai setara x <= 1dan menghasilkan yang sederhana cmpl; setbe. Yang lain membingungkannya dan membuatnya menghasilkan kode yang lebih buruk.
Hobbs
13
x <= 1 atau x <2 lebih sederhana.
gnasher729
9
@Kevin True untuk C ++, karena standar itu berusaha sangat, sangat keras untuk membuatnya tidak mungkin untuk menulis kode yang sesuai. Untungnya ini adalah pertanyaan tentang C #;)
Voo
5
Kebanyakan kompiler modern sudah dapat mengoptimalkan perbandingan seperti ini meskipun saya tidak tahu seberapa pintar kompiler C # dan .NET JITter. Hanya satu perbandingan yang diperlukan dalam kode sebenarnya
phuclv
78

Karena argumen adalah uint( unsigned ), Anda dapat meletakkannya

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

Kurang terbaca (IMHO) tetapi jika Anda menghitung setiap karakter ( Code Golf atau sejenisnya)

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

Edit : untuk pertanyaan yang Anda edit :

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

Atau

  return N < 2 ? 1 : fibn(N-1) + fibn(N-2);
Dmitry Bychenko
sumber
12
Jika itu Code Golf, itu pasti return N<2?1:f(N-1)+f(n-2). : P
Conor O'Brien
36

Anda juga dapat memeriksa bahwa semua bit lainnya adalah 0 seperti ini:

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

Untuk kelengkapan, terima kasih kepada Matt , solusi yang lebih baik:

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

Dalam kedua kasus, Anda perlu menggunakan tanda kurung karena operator bitwise memiliki prioritas lebih rendah daripada ==.

René Vogt
sumber
Saya suka itu! Terima kasih.
pengguna6048670
15
1 lebih sedikit karakter:(N|1)==1
Matt
1
@atk 3 | 1 adalah 3 karena b0011 | b0001 adalah b0011
René Vogt
3
@atk Ini bitwise atau, tidak logis atau. Tidak ada arus pendek.
isaacg
2
@Hoten Benar, tapi Matt mengatakan 1 karakter kurang , bukan 1 operasi kurang .
Ivan Stoev
20

Jika yang ingin Anda lakukan adalah membuat fungsi lebih efisien, gunakan tabel pencarian. Tabel pemeta ternyata sangat kecil, hanya 47 entri - entri berikutnya akan meluap bilangan bulat 32-bit yang tidak ditandatangani. Itu juga tentu saja membuat fungsi menulis menjadi sepele.

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

Anda jelas dapat melakukan hal yang sama untuk faktorial.

Adam
sumber
14

Bagaimana melakukannya dengan bitshift

Jika Anda ingin menggunakan bitshift dan membuat kodenya tidak jelas (tetapi singkat), Anda dapat melakukan:

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

Untuk integer unsigned Ndalam bahasa c,N>>1 membuang bit orde rendah. Jika hasilnya bukan nol, ini berarti N lebih besar dari 1.

Catatan: algoritma ini sangat tidak efisien karena tidak perlu menghitung ulang nilai dalam urutan yang telah dihitung.

Sesuatu JAUH lebih cepat

Hitunglah satu kali lintasan daripada secara implisit membangun pohon berukuran 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;
}

Seperti yang telah disebutkan beberapa orang, tidak butuh waktu lama untuk meluap bahkan 64 bit unsigned integer. Bergantung pada seberapa besar Anda mencoba pergi, Anda harus menggunakan bilangan bulat presisi arbitrer.

Matthew Gunn
sumber
1
Tidak hanya menghindari pohon yang tumbuh secara eksponensial, tetapi Anda juga menghindari potensi percabangan operator terner yang dapat menyumbat pipeline CPU modern.
mathreadler
2
Kode 'cara lebih cepat' Anda tidak akan berfungsi di C # karena uinttidak dapat di-cast secara implisit bool, dan pertanyaannya secara khusus diberi tag sebagai C #.
Pharap
1
@pharap lalu lakukan --N != 0saja. Intinya adalah sesuatu yang O (n) lebih disukai daripada O (fibn (n)).
Matthew Gunn
1
untuk memperluas poin @ MatthewGunn, O (fib (n)) adalah O (phi ^ n) (lihat derivasi ini stackoverflow.com/a/360773/2788187 )
Connor Clark
@ RenéVogt Saya bukan pengembang # ac #. Saya kebanyakan mencoba mengomentari absurditas lengkap dari algoritma O (fibn (N)). Apakah sekarang dikompilasi? (Saya menambahkan! = 0 karena c # tidak memperlakukan hasil bukan nol sebagai benar.) Ia bekerja (dan bekerja) di c langsung jika Anda mengganti uint dengan sesuatu yang standar seperti uint64_t.
Matthew Gunn
10

Saat Anda menggunakan uint, yang tidak bisa negatif, Anda dapat memeriksa apakah n < 2

EDIT

Atau untuk kasus fungsi khusus itu Anda dapat menuliskannya sebagai berikut:

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

yang akan memberikan hasil yang sama, tentu saja dengan biaya langkah rekursi tambahan.

derpirscher
sumber
4
@CatthalMF: tetapi hasilnya sama, karena1 * fibn(0) = 1 * 1 = 1
derpirscher
3
Bukankah fungsi Anda menghitung faktorial, bukan fibonacci?
Barmar
2
@Barmar ya, memang itu faktorial, karena itu pertanyaan awal
derpirscher
3
Mungkin lebih baik tidak menyebutnya fibnbegitu
pie3636
1
@ pie3636 saya menyebutnya fibn karena begitulah sebutannya dalam pertanyaan asli dan saya tidak memperbarui jawabannya nanti
derpirscher
6

Cukup periksa untuk melihat apakah N<= 1 karena Anda tahu N tidak bertanda tangan, hanya ada 2 kondisi N <= 1yang menghasilkan TRUE: 0 dan 1

public uint fibn ( uint N ) 
{
   return (N <= 1) ? 1 : fibn(N-1) + finb(N-2);
}
james
sumber
Apakah penting jika itu ditandatangani atau tidak? Algoritme menghasilkan rekursi tak terbatas dengan input negatif, jadi tidak ada salahnya memperlakukannya setara dengan 0 atau 1.
Barmar
@Barmar yakin itu penting, terutama dalam kasus khusus ini. OP bertanya apakah dia bisa menyederhanakan dengan tepat (N == 0 || N == 1). Anda tahu itu tidak akan kurang dari 0 (karena itu akan ditandatangani!), Dan maksimal bisa 1. N <= 1menyederhanakannya. Saya kira tipe unsigned tidak dijamin, tapi itu harus ditangani di tempat lain, menurut saya.
James
Maksud saya adalah jika itu dideklarasikan int N, dan Anda menyimpan kondisi aslinya, itu akan berulang tanpa batas ketika N negatif dengan kondisi aslinya. Karena itu perilaku yang tidak terdefinisi, kita sebenarnya tidak perlu khawatir tentang itu. Jadi kita dapat mengasumsikan bahwa N adalah non-negatif, terlepas dari deklarasi tersebut.
Barmar
Atau kita dapat melakukan apapun yang kita inginkan dengan masukan negatif, termasuk memperlakukannya sebagai kasus dasar rekursi.
Barmar
@Barmar cukup yakin uint akan selalu diubah menjadi unsigned jika Anda mencoba untuk mengatur ke negatif
james
6

Penafian: Saya tidak tahu C #, dan tidak menguji kode ini:

Tapi saya bertanya-tanya apakah saya bisa membuat ini lebih ringkas dan efisien dengan mengubah [...] menjadi satu perbandingan ...

Tidak perlu bitshifting atau semacamnya, ini hanya menggunakan satu perbandingan, dan seharusnya jauh lebih efisien (O (n) vs O (2 ^ n) menurut saya?). Badan fungsinya lebih kompak , meskipun berakhir menjadi sedikit lebih lama dengan deklarasi.

(Untuk menghilangkan overhead dari rekursi, ada versi iteratif, seperti dalam jawaban Mathew Gunn )

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: Ini adalah pola fungsional umum untuk iterasi dengan akumulator. Jika Anda mengganti N--dengan N-1Anda secara efektif tidak menggunakan mutasi, yang membuatnya dapat digunakan dalam pendekatan fungsional murni.

fede s.
sumber
4

Inilah solusi saya, tidak banyak yang bisa mengoptimalkan fungsi sederhana ini, sebaliknya yang saya tawarkan di sini adalah keterbacaan sebagai definisi matematis dari fungsi rekursif.

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

        case  1: return 1;

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

Definisi matematis dari bilangan Fibonacci dengan cara yang sama ..

masukkan deskripsi gambar di sini

Mengambil lebih jauh untuk memaksa switch case untuk membangun tabel pencarian.

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);
    }
}
Khaled.K
sumber
1
Keuntungan dari solusi Anda adalah bahwa solusi tersebut hanya dihitung saat diperlukan. Yang terbaik adalah tabel pencarian. bonus alternatif: f (n-1) = someCalcOf (f (n-2)), jadi tidak perlu menjalankan ulang secara lengkap.
Karsten
@Karsten Saya telah menambahkan nilai yang cukup untuk sakelar untuk membuat tabel pencarian untuknya. Saya tidak yakin tentang cara kerja bonus alternatif.
Khaled.K
1
Bagaimana ini menjawab pertanyaan?
Clark Kent
@SaviourSelf itu turun ke tabel pencarian, dan ada aspek visual yang dijelaskan dalam jawabannya. stackoverflow.com/a/395965/2128327
Khaled.K
Mengapa Anda menggunakan a switchketika Anda dapat memiliki serangkaian jawaban?
Nayuki
4

untuk N adalah uint, gunakan saja

N <= 1
yanghaogn
sumber
Persis seperti yang saya pikirkan; N adalah uint! Ini harus menjadi jawabannya, sungguh.
squashed.bugaboo
1

Jawaban Dmitry adalah yang terbaik tetapi jika itu adalah tipe pengembalian Int32 dan Anda memiliki kumpulan bilangan bulat yang lebih besar untuk dipilih, Anda dapat melakukan ini.

return new List<int>() { -1, 0, 1, 2 }.Contains(N) ? 1 : N * fibn(N-1);
CathalMF
sumber
2
Bagaimana bisa lebih pendek dari aslinya?
MCMastery
2
@MCMastery Ini tidak lebih pendek. Seperti yang saya sebutkan hanya lebih baik jika tipe pengembalian asli adalah int32 dan dia memilih dari sekumpulan besar angka yang valid.
Alih
1
Alasan OP tampaknya terkait dengan pengoptimalan. Ini adalah ide yang buruk karena beberapa alasan: 1) membuat instance objek baru di dalam setiap panggilan rekursif adalah ide yang sangat buruk, 2) List.Containsadalah O (n), 3) hanya membuat dua perbandingan ( N > -3 && N < 3) akan memberikan kode yang lebih pendek dan lebih mudah dibaca.
Groo
@Groo Dan bagaimana jika nilainya adalah -10, -2, 5, 7, 13
CathalMF
Bukan itu yang diminta OP. Tapi bagaimanapun, Anda masih 1) tidak ingin membuat instance baru di setiap panggilan, 2) lebih baik menggunakan (tunggal) hashset sebagai gantinya, 3) untuk masalah tertentu, Anda juga akan dapat mengoptimalkan fungsi hash untuk menjadi murni, atau bahkan menggunakan operasi bitwise yang diatur dengan cerdik seperti yang disarankan dalam jawaban lain.
Groo
0

Deret Fibonacci adalah rangkaian angka di mana sebuah angka ditemukan dengan menjumlahkan dua angka sebelumnya. Ada dua jenis titik awal: ( 0,1 , 1,2, ..) dan ( 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
-----------------------------------------

Posisi Ndalam kasus ini dimulai dari 1, bukan0-based sebagai indeks array.

Dengan menggunakan fitur tubuh ekspresi C # 6 dan saran Dmitry tentang operator terner kita dapat menulis fungsi satu baris dengan perhitungan yang benar untuk tipe 1:

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

dan untuk tipe 2:

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

Agak terlambat ke pesta, tetapi Anda juga bisa melakukannya (x==!!x)

!!xmengonversi nilai menjadi 1jika tidak 0, dan membiarkannya 0jika memang demikian.
Saya sering menggunakan hal semacam ini dalam kebingungan C.

Catatan: Ini C, tidak yakin apakah itu berfungsi di C #

Satu Malam Normal
sumber
4
Tidak yakin mengapa ini mendapat suara positif. Bahkan sepintas mencoba ini seperti uint n = 1; if (n == !!n) { }memberikan Operator '!' cannot be applied to operand of type 'uint'pada !nC #. Hanya karena sesuatu bekerja di C tidak berarti bekerja di C #; bahkan #include <stdio.h>tidak bekerja di C #, karena C # tidak memiliki arahan preprocessor "include". Bahasanya lebih berbeda daripada C dan C ++.
CVn
2
Oh. Baik. Maaf :(
One Normal Night
@OneNormalNight (x == !! x) Bagaimana ini akan bekerja. Perhatikan masukan saya adalah 5. (5 == !! 5). Ini akan memberikan hasil yang benar
VINOTH ENERGETIC
1
@VinothKumar !! 5 mengevaluasi ke 1. (5 == !! 5) mengevaluasi (5 == 1) yang mengevaluasi ke salah.
Satu Malam Normal
@OneNormalNight ya saya mengerti sekarang. ! (5) memberikan 1 lagi diterapkan itu memberi 0. Bukan 1.
VINOTH ENERGETIC
-3

Jadi saya membuat Listbilangan bulat khusus ini dan memeriksa apakah Nberkaitan dengannya.

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

Anda juga dapat menggunakan metode ekstensi untuk tujuan berbeda di mana Containshanya dipanggil sekali (misalnya, saat aplikasi Anda mulai dan memuat data). Ini memberikan gaya yang lebih jelas dan menjelaskan hubungan utama dengan nilai Anda ( 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);
    }
}

Terapkan itu:

N.PertainsTo(ints)

Ini mungkin bukan cara tercepat untuk melakukannya, tetapi bagi saya, ini tampaknya gaya yang lebih baik.

KnorxThieus
sumber