Можно ли упростить (x == 0 || x == 1) в одну операцию?

Итак, я пытался записать n-е число в последовательности Фибоначчи как можно более компактную функцию:

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

Но мне интересно, могу ли я сделать это еще более компактным и эффективным, изменив

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

в одно сравнение. Есть ли какая-нибудь фантастическая операция сдвига бит, которая может это сделать?

Ответ 1

Это также работает

Math.Sqrt(N) == N 

квадратный корень из 0 и 1 будет возвращать 0 и 1 соответственно.

Ответ 2

Существует несколько способов реализации вашего арифметического теста с использованием побитовой арифметики. Ваше выражение:

  • x == 0 || x == 1

логически эквивалентно каждому из них:

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

Bonus:

  • x * x == x (доказательство требует немного усилий)

Но, на самом деле, эти формы являются наиболее читаемыми, а крошечная разница в производительности не стоит использовать поразрядную арифметику:

  • x == 0 || x == 1
  • x <= 1 (потому что x - целое без знака)
  • x < 2 (потому что x - целое без знака)

Ответ 3

Поскольку аргумент uint (без знака), вы можете поместить

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

Менее читаемый (IMHO), но если вы считаете каждый символ (Code Golf или похожий)

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

Ответ 4

Вы также можете проверить, что все остальные биты равны 0:

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

Для полноты благодаря Matt еще лучшее решение:

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

В обоих случаях вам нужно позаботиться о скобках, потому что побитовые операторы имеют более низкий приоритет, чем ==.

Ответ 5

Если вы хотите сделать функцию более эффективной, используйте таблицу поиска. Таблица поиска удивительно мала только в 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];
    }
}

Очевидно, вы можете сделать то же самое для факториалов.

Ответ 6

Как это сделать с битрейтом

Если вы хотите использовать bithift и сделать код несколько неясным (но коротким), вы можете сделать:

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

Для целых чисел без знака N в языке c, N>>1 отбрасывает бит младшего порядка. Если этот результат отличен от нуля, это означает, что N больше 1.

Примечание: этот алгоритм ужасно неэффективен, так как он излишне пересчитывает значения в уже вычисленной последовательности.

Что-то WAY WAY быстрее

Рассчитайте его на один проход, а не на неявное построение дерева размером с фибонач (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-разрядного беззнакового целого числа не требуется много времени. В зависимости от того, насколько велика ваша задача, вам нужно использовать произвольные целые числа точности.

Ответ 7

Поскольку вы используете uint, который не может получить отрицательный результат, вы можете проверить, есть ли n < 2

ИЗМЕНИТЬ

Или для этого случая специальных функций вы можете записать его следующим образом:

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

что приведет к такому же результату, конечно, за счет дополнительного этапа рекурсии.

Ответ 8

Просто проверьте, есть ли N is <= 1, поскольку вы знаете, что N без знака, могут быть только 2 условия, которые N <= 1 приводят к TRUE: 0 и 1

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

Ответ 9

Отказ от ответственности: я не знаю С# и не тестировал этот код:

Но мне интересно, могу ли я сделать это еще более компактным и эффективным, изменив [...] на одно сравнение...

Не нужно битрейта или такого, это использует только одно сравнение, и оно должно быть намного более эффективным (O (n) vs 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, вы не будете использовать мутацию, что делает ее пригодной для использования в чисто функциональном подходе.

Ответ 10

Здесь мое решение, мало оптимизируя эту простую функцию, с другой стороны, то, что я предлагаю здесь, является читабельностью как математическое определение рекурсивной функции.

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

Ответ 11

Ответ Дмитрия лучше, но если это был тип возврата Int32, и у вас был большой набор целых чисел, которые вы могли бы выбрать, вы могли бы это сделать.

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

Ответ 12

для N является uint, просто используйте

N <= 1

Ответ 13

Последовательность Фибоначчи представляет собой ряд чисел, где число найдено, складывая два числа перед ним. Существует два типа исходных точек: ( 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 как индекс массива.

Использование С# 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);

Ответ 14

Бит опоздал на вечеринку, но вы также можете сделать (x==!!x)

!!x преобразует значение a в 1, если оно не 0, и оставляет его в 0, если оно есть.
Я использую эту любопытную вещь в C obfuscation много.

Примечание: это C, не уверен, что он работает в С#

Ответ 15

Итак, я создал 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)

Это может быть не самый быстрый способ сделать это, но для меня он выглядит лучше.