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

9
이건 정말 피보나치가 아니죠?
n8wrl

9
fibonaci는 이전 두 값을 더합니다. 당신을 찾으시는 것 fibn(N-1) + fibn(N-2) 대신에 N * fibn(N-1)?
juharr

46
나는 모두 나노초를 줄이려고하지만 재귀를 사용하는 방법에서 간단한 비교를한다면 비교의 효율성에 노력을 기울이고 재귀는 그대로 두어야할까요?
Jon Hanna

25
재귀 적 방법을 사용하여 Fabonacci 수를 계산 한 다음 성능을 향상시키고 싶습니까? 루프로 변경하지 않는 이유는 무엇입니까? 아니면 빠른 전력을 사용합니까?
notbad

답변:


-9

이것도 작동합니다

Math.Sqrt(N) == N 

0과 1의 제곱근은 각각 0과 1을 반환합니다.


20
Math.Sqrt복잡한 부동 소수점 함수입니다. 정수 전용 대안에 비해 느리게 실행됩니다 !!
Nayuki

1
이것은 깨끗해 보이지만 다른 답변을 확인하면 이것보다 더 좋은 방법이 있습니다.
Mafii

9
내가 작업중인 코드에서이 문제를 발견했다면 최소한 그 사람의 책상으로 걸어가 당시에 어떤 물질을 소비하고 있었는지 뾰족하게 물어볼 것입니다.
CVn

누가 올바른 마음으로 이것을 답으로 표시 했습니까? 말 못하는.
squashed.bugaboo

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 < 2( x부호없는 정수 이기 때문에 )

6
잊지 마세요(x & ~1) == 0
Lee Daniel Crocker

71
그러나 그들 중 어떤 특정한 것이 "더 효율적"이라고 내기하지 마십시오. gcc는 실제로 x == 0 || x == 1for (x & ~1) == 0또는 보다 적은 코드를 생성 (x | 1) == 1합니다. 첫 번째의 경우 동일한 것으로 인식 x <= 1하고 간단한 cmpl; setbe. 다른 사람들은 그것을 혼동하고 더 나쁜 코드를 생성합니다.
hobbs

13
x <= 1 또는 x <2가 더 간단합니다.
gnasher729

9
@Kevin True for C ++, 그 표준은 준수 코드를 작성하는 것을 불가능하게 만들기 위해 정말 열심히 노력하기 때문입니다. 다행히 C #에 대한 질문입니다.)
Voo

5
대부분의 현대 컴파일러는 이미 이와 같은 비교를 최적화 할 수 있지만 C # 컴파일러와 .NET JITter가 얼마나 똑똑한지는 모르겠습니다. 실제 코드에서는 단 하나의 비교 만 필요합니다.
phuclv 2011

78

인수가 uint( unsigned ) 이기 때문에

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

Matt 덕분에 완성도를 높이 려면 더 나은 솔루션 :

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

두 경우 모두 비트 연산자가.보다 우선 순위가 낮기 때문에 괄호를 처리해야합니다 ==.


나는 그것을 좋아한다! 감사.
user6048670

15
문자 1 개 감소 :(N|1)==1
Matt

1
B0001 b0011입니다 | 3 @atk | b0011이 때문에 1 3
르네 보그

3
@atk 이것은 비트 단위이거나 논리적이 아닙니다. 단락이 없습니다.
isaacg

2
@ Hoten 맞았지만 Matt는 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 경우 하위 비트를 버립니다. 그 결과가 0이 아니면 N이 1보다 크다는 것을 의미합니다.

참고 :이 알고리즘은 이미 계산 된 시퀀스의 값을 불필요하게 다시 계산하므로 매우 비효율적입니다.

더 빠른 방법

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

일부 사람들이 언급했듯이 64 비트 부호없는 정수도 오버플로하는 데 오래 걸리지 않습니다. 얼마나 큰지에 따라 임의의 정밀도 정수를 사용해야합니다.


1
기하 급수적으로 증가하는 트리를 피할뿐만 아니라 최신 CPU 파이프 라인을 막을 수있는 삼항 연산자의 잠재적 분기도 피할 수 있습니다.
mathreadler

2
uint은 암시 적으로 캐스트 할 수 없기 때문에 '훨씬 더 빠른'코드는 C #에서 작동 하지 않으며 bool질문은 구체적으로 C #으로 태그가 지정됩니다.
Pharap

1
@pharap 그런 다음 --N != 0대신하십시오. 요점은 O (n)가 O (fibn (n))보다 바람직하다는 것입니다.
마태 복음 건

1
@MatthewGunn의 요점을 확장하려면 O (fib (n))은 O (phi ^ n)입니다 (이 파생 참조 stackoverflow.com/a/360773/2788187 )
Connor Clark

@ RenéVogt 저는 ac # 개발자가 아닙니다. 나는 주로 O (fibn (N)) 알고리즘의 완전한 부조리에 대해 언급하려고했습니다. 지금 컴파일합니까? (c #은 0이 아닌 결과를 true로 처리하지 않기 때문에! = 0을 추가했습니다.) uint를 uint64_t와 같은 표준으로 바꾸면 직선 c에서 작동 (및 작동)됩니다.
Matthew Gunn 2016

10

음수가 될 수없는 단위를 사용하면 다음 사항을 확인할 수 있습니다. 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 2016-04-05

2
그 때문에 예 @Barmar, 실제로 원래의 질문은, 계승의이
derpirscher

3
fibn그때 부르지 않는 것이 최선일 수 있습니다
pie3636

1
@ pie3636 나는 fibn라는이 원래의 질문으로 불렸다 내가 나중에 답을 업데이트하지 않았습니다 어떻게 때문에
derpirscher

6

NN이 서명되지 않았 음을 알기 때문에 <= 1 인지 확인하기 만하면 N <= 1됩니다 TRUE. 결과는 0과 1입니다.

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

서명 여부에 관계없이 중요합니까? 이 알고리즘은 음수 입력으로 무한 재귀를 생성하므로 0 또는 1과 동일하게
처리해

@Barmar는 특히이 특정 경우에 중요합니다. OP는 그가 정확하게 단순화 할 수 있는지 물었다 (N == 0 || N == 1). 당신은 그것이 0보다 작지 않을 것이라는 것을 알고 있습니다 (왜냐하면 그것은 서명 될 것이기 때문입니다!). 그리고 최대 값은 1이 될 수 있습니다 N <= 1. 서명되지 않은 유형은 보장되지 않지만 다른 곳에서 처리해야한다고 생각합니다.
james

내 요점은 선언 int N되고 원래 조건을 유지하면 N이 원래 조건과 함께 음수 일 때 무한히 반복된다는 것입니다. 이는 정의되지 않은 동작이므로 실제로 걱정할 필요가 없습니다. 따라서 선언에 관계없이 N이 음이 아니라고 가정 할 수 있습니다.
Barmar

또는 부정적인 입력을 재귀의 기본 케이스로 처리하는 것을 포함하여 원하는 모든 것을 할 수 있습니다.
Barmar

@Barmar는 음수로 설정하려고하면 uint가 항상 unsigned로 변환 될 것이라고 확신합니다.
james

6

면책 조항 : C #을 모르고이 코드를 테스트하지 않았습니다.

그러나 [...]를 단일 비교로 변경하여 이것을 더욱 간결하고 효율적으로 만들 수 있는지 궁금합니다.

비트 시프 팅 등이 필요하지 않습니다. 이것은 하나의 비교 만 사용하며 훨씬 더 효율적 이어야합니다 (O (n) 대 O (2 ^ n) 제 생각에는?). 기능의 본체가 더 콤팩트합니다. 하지만 선언과 함께 조금 더 길어집니다.

(재귀에서 오버 헤드를 제거하기 위해 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

추신 : 이것은 누산기 반복을위한 일반적인 기능 패턴입니다. 대체 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

1
이것은 질문에 어떻게 대답합니까?
Clark Kent

@SaviourSelf는 룩업 테이블로 내려 가고 대답에 설명 된 시각적 측면이 있습니다. stackoverflow.com/a/395965/2128327
Khaled.K

switch답변 배열을 가질 수 있는데 왜를 사용 합니까?
Nayuki

4

N이 단위이기 때문에

N <= 1

정확히 내가 생각했던 것입니다. N은 단위입니다! 이것은 정말로 답이되어야합니다.
squashed.bugaboo

1

Dmitry의 대답이 가장 좋지만 Int32 반환 유형이고 선택할 수있는 더 큰 정수 세트가 있다면 이렇게 할 수 있습니다.

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

2
원본보다 어떻게 짧습니까?
MCMastery

2
@MCMastery 짧지 않습니다. 내가 언급했듯이 원래 반환 유형이 int32이고 유효한 숫자 집합에서 선택하는 경우에만 더 좋습니다. 쓸 필요가
없어짐

1
OP의 이유는 최적화와 관련된 것 같습니다. 이것은 몇 가지 이유로 나쁜 생각입니다. 1) 각 재귀 호출 내에서 새 객체를 인스턴스화하는 것은 정말 나쁜 생각이고, 2) List.ContainsO (n), 3) 단순히 두 번 비교 ( N > -3 && N < 3)하면 코드가 더 짧고 읽기 쉬워집니다.
Groo

@Groo 그리고 만약 값이 -10, -2, 5, 7, 13
이라면?

OP가 요청한 것이 아닙니다. 그러나 어쨌든 여전히 1) 각 호출에서 새 인스턴스를 만들고 싶지 않을 것입니다. 2) 대신 (단일) 해시 세트를 사용하는 것이 더 좋을 것입니다. 3) 특정 문제에 대해 해시 함수를 최적화 할 수도 있습니다. 순수하거나 다른 답변에서 제안한 것처럼 영리하게 배열 된 비트 연산을 사용하십시오.
Groo

0

피보나치 수열은 앞의 두 숫자를 더하여 숫자를 찾는 일련의 숫자입니다. 시작점에는 ( 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
-----------------------------------------

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
오. 괜찮아. 미안합니다 :(
One Normal Night

@OneNormalNight (x == !! x) 작동 방식. 내 입력이 5라고 생각하십시오 (5 == !! 5). 그것은 사실로 결과를 줄 것입니다
VINOTH ENERGETIC

1
@VinothKumar !! 5는 1로 평가됩니다. (5 == !! 5)는 false로 평가되는 (5 == 1)을 평가합니다.
One Normal Night

@OneNormalNight 예, 지금 얻었습니다. ! (5) 다시 1을 적용하면 0이됩니다. 1이 아닙니다.
VINOTH ENERGETIC

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

또한 Contains한 번만 호출 되는 다른 목적으로 확장 메서드를 사용할 수도 있습니다 (예 : 응용 프로그램이 시작되고 데이터를로드 할 때). 이는보다 명확한 스타일을 제공하고 귀하의 가치 ( 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.