Có thể đơn giản hóa (x == 0 || x == 1) thành một phép toán không?


106

Vì vậy, tôi đã cố gắng viết số thứ n trong dãy Fibonacci trong một hàm càng nhỏ gọn càng tốt:

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

Nhưng tôi tự hỏi liệu tôi có thể làm cho điều này nhỏ gọn và hiệu quả hơn bằng cách thay đổi

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

thành một so sánh duy nhất. Có một số hoạt động thay đổi bit ưa thích có thể làm điều này không?


111
Tại sao? Nó có thể đọc được, mục đích rất rõ ràng và nó không đắt. Tại sao lại thay đổi nó thành một số đối sánh mẫu bit "thông minh" khó hiểu hơn và không xác định rõ mục đích?
D Stanley

9
Đây không thực sự là fibonaci đúng không?
n8wrl

9
fibonaci thêm hai giá trị trước đó. Ý bạn là fibn(N-1) + fibn(N-2) thay vì N * fibn(N-1)?
juharr

46
Tôi là tất cả để loại bỏ nano giây, nhưng nếu bạn có một phép so sánh đơn giản trong một phương pháp sử dụng đệ quy, tại sao lại tốn công sức vào hiệu quả của phép so sánh và để đệ quy ở đó?
Jon Hanna

25
Bạn sử dụng một cách đệ quy để tính số Fabonacci, sau đó bạn muốn cải thiện hiệu suất? Tại sao không thay đổi nó thành một vòng lặp? hay sử dụng nguồn điện nhanh?
notbad

Câu trả lời:


-9

Cái này cũng hoạt động

Math.Sqrt(N) == N 

căn bậc hai của 0 và 1 sẽ trả về 0 và 1 tương ứng.


20
Math.Sqrtlà một hàm dấu phẩy động phức tạp. Nó chạy chậm so với các lựa chọn thay thế chỉ số nguyên !!
Nayuki

1
Điều này trông có vẻ rõ ràng, nhưng có nhiều cách tốt hơn cách này nếu bạn kiểm tra các câu trả lời khác.
Mafii

9
Nếu tôi bắt gặp điều này trong bất kỳ đoạn mã nào mà tôi đang làm việc, ít nhất tôi có thể đi đến bàn của người đó và hỏi thẳng họ xem họ đang tiêu thụ chất gì vào thời điểm đó.
một CVn

Ai trong tâm trí của họ, đã đánh dấu đây là câu trả lời? Không nói nên lời.
squashed.bugaboo

212

Có một số cách để thực hiện kiểm tra số học của bạn bằng cách sử dụng số học bit. Biểu hiện của bạn:

  • x == 0 || x == 1

về mặt logic tương đương với từng cái sau:

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

Tặng kem:

  • x * x == x (việc chứng minh cần một chút nỗ lực)

Nhưng thực tế mà nói, các biểu mẫu này là dễ đọc nhất và sự khác biệt nhỏ về hiệu suất không thực sự đáng để sử dụng số học bit:

  • x == 0 || x == 1
  • x <= 1(vì xlà số nguyên không dấu)
  • x < 2(vì xlà số nguyên không dấu)

6
Đừng quên(x & ~1) == 0
Lee Daniel Crocker

71
Nhưng đừng đặt cược vào bất kỳ một trong số chúng cụ thể là "hiệu quả hơn". gcc thực sự tạo ra ít mã x == 0 || x == 1hơn cho (x & ~1) == 0hoặc (x | 1) == 1. Đối với cái đầu tiên, nó đủ thông minh để nhận ra nó tương đương với x <= 1và xuất ra đơn giản cmpl; setbe. Những người khác nhầm lẫn nó và làm cho nó tạo ra mã tồi tệ hơn.
hobbs

13
x <= 1 hoặc x <2 thì đơn giản hơn.
gnasher729

9
@Kevin Đúng cho C ++, bởi vì tiêu chuẩn đó cố gắng thực sự, rất khó để làm cho không thể viết mã tuân thủ. May mắn thay, đây là một câu hỏi về C #;)
Voo

5
Hầu hết các trình biên dịch hiện đại đã có thể tối ưu hóa các so sánh như thế này mặc dù tôi không biết trình biên dịch C # và .NET JITter thông minh như thế nào. Chỉ có một so sánh duy nhất là cần có trong mã thực
phuclv

78

Vì đối số là uint( không có dấu ) nên bạn có thể đặt

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

Ít đọc được (IMHO) nhưng nếu bạn đếm từng ký tự ( Mã Golf hoặc tương tự)

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

Chỉnh sửa : cho câu hỏi đã chỉnh sửa của bạn :

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

Hoặc là

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

12
Nếu đó là Code Golf, nó sẽ là return N<2?1:f(N-1)+f(n-2). : P
Conor O'Brien

36

Bạn cũng có thể kiểm tra xem tất cả các bit khác đều là 0 như sau:

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

Để hoàn thiện nhờ Matt , giải pháp thậm chí còn tốt hơn:

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

Trong cả hai trường hợp, bạn cần quan tâm đến dấu ngoặc đơn vì các toán tử bitwise có mức độ ưu tiên thấp hơn ==.


Tôi thích nó! Cảm ơn.
user6048670

15
1 ký tự ít hơn:(N|1)==1
Matt

1
@atk 3 | 1 là 3 vì b0011 | b0001 là b0011
René Vogt

3
@atk Đây là bitwise hoặc, không logic hoặc. Không có đường ngắn.
isaacg

2
@Hoten Đúng, nhưng Matt đã nói bớt 1 ký tự chứ không phải 1 phép toán ít hơn .
Ivan Stoev

20

Nếu những gì bạn muốn làm là làm cho chức năng hiệu quả hơn, thì hãy sử dụng bảng tra cứu. Bảng tra cứu nhỏ một cách đáng ngạc nhiên chỉ có 47 mục nhập - mục nhập tiếp theo sẽ làm tràn một số nguyên không dấu 32 bit. Tất nhiên, nó cũng làm cho hàm trở nên tầm thường khi viết.

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

Rõ ràng bạn có thể làm điều tương tự đối với các giai thừa.


14

Làm thế nào để làm điều đó với bithift

Nếu bạn muốn sử dụng bithift và làm cho mã hơi tối nghĩa (nhưng ngắn gọn), bạn có thể làm:

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

Đối với một số nguyên không dấu Ntrong ngôn ngữ c,N>>1 bỏ bit thứ tự thấp. Nếu kết quả đó khác 0, điều đó có nghĩa là N lớn hơn 1.

Lưu ý: thuật toán này kém hiệu quả kinh khủng vì nó không cần thiết phải tính toán lại các giá trị trong chuỗi đã được tính toán.

Một cái gì đó WAY WAY nhanh hơn

Tính toán nó một lần thay vì hoàn toàn xây dựng một cây có kích thước 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;
}

Như một số người đã đề cập, không mất nhiều thời gian để làm tràn ngay cả một số nguyên 64 bit không dấu. Tùy thuộc vào mức độ lớn bạn đang cố gắng, bạn sẽ cần sử dụng các số nguyên chính xác tùy ý.


1
Không chỉ tránh cây đang phát triển theo cấp số nhân, mà bạn còn tránh được sự phân nhánh tiềm ẩn của toán tử bậc ba có thể làm tắc nghẽn các đường ống dẫn CPU hiện đại.
mathreadler

2
Mã 'cách nhanh hơn' của bạn sẽ không hoạt động trong C # vì uintkhông thể truyền ngầm được boolvà câu hỏi được gắn thẻ cụ thể là C #.
Pharap

1
@pharap sau đó làm --N != 0thay thế. Vấn đề là cái gì đó O (n) được ưu tiên hơn O (fibn (n)).
Matthew Gunn

1
để mở rộng quan điểm của @ MatthewGunn, O (fib (n)) là O (phi ^ n) (xem dẫn xuất này stackoverflow.com/a/360773/2788187 )
Connor Clark

@ RenéVogt Tôi không phải là nhà phát triển ac #. Tôi chủ yếu cố gắng bình luận về sự vô lý hoàn toàn của thuật toán O (fibn (N)). Nó có biên dịch bây giờ không? (Tôi đã thêm! = 0 vì c # không coi các kết quả khác 0 là đúng.) Nó hoạt động (và hoạt động) trong c nếu bạn thay thế uint bằng một thứ gì đó tiêu chuẩn như uint64_t.
Matthew Gunn

10

Khi bạn sử dụng uint, không thể bị âm, bạn có thể kiểm tra xem n < 2

BIÊN TẬP

Hoặc đối với trường hợp hàm đặc biệt đó, bạn có thể viết nó như sau:

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

điều này sẽ dẫn đến cùng một kết quả, tất nhiên với chi phí của một bước đệ quy bổ sung.


4
@CatthalMF: nhưng kết quả giống nhau, bởi vì1 * fibn(0) = 1 * 1 = 1
derpirscher

3
Không phải hàm của bạn đang tính giai thừa, không phải fibonacci?
Barmar

2
@Barmar vâng, thực sự đó là thừa, bởi vì đó là câu hỏi ban đầu
derpirscher

3
Có thể là tốt nhất không nên gọi nó là fibnsau đó
pie3636

1
@ pie3636 tôi gọi nó là fibn bởi vì đó là cách nó được gọi là trong câu hỏi ban đầu và tôi không cập nhật các câu trả lời sau này
derpirscher

6

Đơn giản chỉ cần kiểm tra xem có phải Nlà <= 1 hay không vì bạn biết N không có dấu, chỉ có thể có 2 điều kiện dẫn N <= 1đến kết quả là TRUE: 0 và 1

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

Nó thậm chí có vấn đề nếu nó được ký hoặc không được ký? Thuật toán tạo ra đệ quy vô hạn với đầu vào âm, vì vậy không có hại gì khi coi chúng tương đương với 0 hoặc 1.
Barmar

@Barmar chắc chắn rằng nó quan trọng, đặc biệt là trong trường hợp cụ thể này. OP hỏi liệu anh ta có thể đơn giản hóa chính xác không (N == 0 || N == 1). Bạn biết rằng nó sẽ không nhỏ hơn 0 (vì sau đó nó sẽ được ký!), Và tối đa có thể là 1. N <= 1đơn giản hóa nó. Tôi đoán loại không dấu không được đảm bảo, nhưng điều đó nên được xử lý ở nơi khác, tôi muốn nói.
james

Quan điểm của tôi là nếu nó đã được khai báo int N, và bạn giữ nguyên điều kiện ban đầu, nó sẽ lặp lại vô hạn khi N âm với điều kiện ban đầu. Vì đó là hành vi không xác định, chúng tôi thực sự không cần phải lo lắng về nó. Vì vậy, chúng ta có thể giả định rằng N là không âm, bất kể khai báo.
Barmar

Hoặc chúng ta có thể làm bất cứ điều gì chúng ta muốn với các đầu vào âm, bao gồm cả việc coi chúng như trường hợp cơ bản của phép đệ quy.
Barmar

@Barmar khá chắc chắn uint sẽ luôn được chuyển đổi thành không dấu nếu bạn cố gắng đặt thành phủ định
james

6

Tuyên bố từ chối trách nhiệm: Tôi không biết C # và đã không kiểm tra mã này:

Nhưng tôi tự hỏi liệu tôi có thể làm cho điều này nhỏ gọn và hiệu quả hơn bằng cách thay đổi [...] thành một so sánh duy nhất ...

Không cần chuyển bit hoặc tương tự, điều này chỉ sử dụng một phép so sánh và nó sẽ hiệu quả hơn rất nhiều (tôi nghĩ là O (n) so với O (2 ^ n)?). Phần thân của hàm này nhỏ gọn hơn , mặc dù nó kết thúc lâu hơn một chút với phần khai báo.

(Để loại bỏ chi phí khỏi đệ quy, có phiên bản lặp lại, như trong câu trả lời của 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: Đây là một mẫu chức năng phổ biến để lặp lại với bộ tích lũy. Nếu bạn thay thế N--bằng N-1bạn đang sử dụng hiệu quả không có đột biến, điều này làm cho nó có thể sử dụng được theo cách tiếp cận chức năng thuần túy.


4

Đây là giải pháp của tôi, không có gì nhiều trong việc tối ưu hóa hàm đơn giản này, mặt khác những gì tôi cung cấp ở đây là khả năng đọc được như một định nghĩa toán học của hàm đệ quy.

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

        case  1: return 1;

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

Định nghĩa toán học của số Fibonacci theo cách tương tự ..

nhập mô tả hình ảnh ở đây

Tiến xa hơn để buộc trường hợp chuyển mạch xây dựng một bảng tra cứu.

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
Ưu điểm của giải pháp của bạn là nó chỉ được tính toán khi cần thiết. Tốt nhất sẽ là một bảng tra cứu. phần thưởng thay thế: f (n-1) = someCalcOf (f (n-2)), vì vậy không cần chạy lại toàn bộ.
Karsten

@Karsten Tôi đã thêm đủ giá trị cho công tắc để tạo bảng tra cứu cho nó. Tôi không chắc về cách thức hoạt động của phần thưởng thay thế.
Khaled.K

1
Làm thế nào để trả lời câu hỏi này?
Clark Kent

@SaviourSelf nó đi xuống bảng tra cứu và có khía cạnh hình ảnh được giải thích trong câu trả lời. stackoverflow.com/a/395965/2128327
Khaled.K

Tại sao bạn lại sử dụng dấu switchkhi bạn có thể có một loạt câu trả lời?
Nayuki

4

đối với N là uint, chỉ cần sử dụng

N <= 1

Chính xác những gì tôi đang nghĩ; N là uint! Đây thực sự phải là câu trả lời.
squashed.bugaboo

1

Câu trả lời của Dmitry là tốt nhất nhưng nếu đó là kiểu trả về Int32 và bạn có một tập hợp các số nguyên lớn hơn để chọn, bạn có thể làm điều này.

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

2
Làm thế nào là ngắn hơn so với ban đầu?
MCMastery

2
@MCMastery Nó không ngắn hơn. Như tôi đã đề cập, nó chỉ tốt hơn nếu kiểu trả về ban đầu là int32 và anh ta đang chọn từ một tập hợp lớn các số hợp lệ. Không cần phải viết (N == -1 || N == 0 || N == 1 || N == 2)
CathalMF

1
Lý do của OP dường như liên quan đến tối ưu hóa. Đây là một ý tưởng tồi vì một số lý do: 1) khởi tạo một đối tượng mới bên trong mỗi cuộc gọi đệ quy là một ý tưởng thực sự tồi, 2) List.Containslà O (n), 3) chỉ cần thực hiện hai phép so sánh thay vào đó ( N > -3 && N < 3) sẽ cung cấp mã ngắn hơn và dễ đọc hơn.
Groo

@Groo Và nếu các giá trị là -10, -2, 5, 7, 13
CathalMF

Đó không phải là những gì OP yêu cầu. Nhưng dù sao, bạn vẫn 1) sẽ không muốn tạo một phiên bản mới trong mỗi cuộc gọi, 2) tốt hơn nên sử dụng một bộ băm (duy nhất) để thay thế, 3) cho một vấn đề cụ thể, bạn cũng có thể tối ưu hóa hàm băm để thuần túy, hoặc thậm chí sử dụng các thao tác bitwise được sắp xếp khéo léo như được đề xuất trong các câu trả lời khác.
Groo

0

Dãy Fibonacci là một dãy số trong đó một số được tìm thấy bằng cách cộng hai số trước nó. Có hai loại điểm bắt đầu: ( 0,1 , 1,2, ..) và ( 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
-----------------------------------------

Vị trí Ntrong trường hợp này bắt đầu từ 1, nó không phải 0-basedlà một chỉ số mảng.

Sử dụng tính năng Expression-body trong C # 6 và gợi ý của Dmitry về toán tử bậc ba, chúng ta có thể viết một hàm một dòng với phép tính đúng cho kiểu 1:

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

và đối với loại 2:

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

-2

Đến bữa tiệc hơi muộn, nhưng bạn cũng có thể làm (x==!!x)

!!xchuyển đổi giá trị thành 1nếu không 0, và giữ nguyên giá trị 0nếu có.
Tôi sử dụng loại này trong C obfuscation rất nhiều.

Lưu ý: Đây là C, không chắc nó có hoạt động trong C # không


4
Không chắc tại sao điều này lại được ủng hộ. Thậm chí lướt qua nỗ lực này là uint n = 1; if (n == !!n) { }cung cấp cho Operator '!' cannot be applied to operand of type 'uint'trên !ntrong C #. Chỉ vì một cái gì đó hoạt động trong C không có nghĩa là nó hoạt động trong C #; thậm chí #include <stdio.h>không hoạt động trong C #, vì C # không có chỉ thị tiền xử lý "include". Các ngôn ngữ khác nhau nhiều hơn là C và C ++.
CVn

2
Oh. Được chứ. Tôi xin lỗi :(
Một đêm bình thường

@OneNormalNight (x == !! x) Cách này sẽ hoạt động. Hãy xem xét đầu vào của tôi là 5. (5 == !! 5). Nó sẽ cho kết quả là đúng
VINOTH tràn đầy năng lượng

1
@VinothKumar !! 5 đánh giá là 1. (5 == !! 5) đánh giá (5 == 1) đánh giá là false.
Một đêm bình thường

@OneNormalNight vâng, tôi đã nhận nó ngay bây giờ. ! (5) cho 1 một lần nữa áp dụng nó cho 0. Không phải 1.
VINOTH ENERGETIC

-3

Vì vậy, tôi đã tạo một Listtrong những số nguyên đặc biệt này và kiểm tra xem có Nliên quan đến nó hay không.

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

Bạn cũng có thể sử dụng một phương thức mở rộng cho các mục đích khác nhau mà Containschỉ được gọi một lần (ví dụ: khi ứng dụng của bạn đang khởi động và tải dữ liệu). Điều này cung cấp một phong cách rõ ràng hơn và làm rõ mối quan hệ chính với giá trị của bạn ( 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);
    }
}

Áp dụng nó:

N.PertainsTo(ints)

Đây có thể không phải là cách nhanh nhất để làm điều đó, nhưng với tôi, nó có vẻ là một phong cách tốt hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.