Чи можна спростити (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 Стенлі

9
Це насправді не фібонаки, правда?
n8wrl

9
fibonaci додає два попередні значення. Ви мали на увазі fibn(N-1) + fibn(N-2) замість N * fibn(N-1)?
juharr

46
Я все за гоління наносекунд, але якщо у вас є просте порівняння в методі, який використовує рекурсію, навіщо витрачати зусилля на ефективність порівняння і залишити рекурсію там?
Джон Ханна

25
Ви використовуєте рекурсивний спосіб обчислення числа Фабоначчі, то хочете покращити продуктивність? Чому б не змінити його в цикл? або використовувати швидку потужність?
нотбад

Відповіді:


-9

Цей теж працює

Math.Sqrt(N) == N 

квадратний корінь 0 і 1 поверне 0 і 1 відповідно.


20
Math.Sqrtє складною функцією з плаваючою комою. Він працює повільно порівняно з цілими лише альтернативами !!
Наюкі

1
Це виглядає чисто, але є кращі способи, ніж це, якщо ви перевірите інші відповіді.
Мафій

9
Якби я натрапив на це в будь-якому коді, над яким я працював, я, швидше за все, хотів би підійти до столу цієї людини і чітко запитати їх, яку речовину вони вживали в той час.
CVn

Хто їх правильним розумом позначив це як відповідь? Безмовна.
сквош.бугабу

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
Лі Даніел Крокер

71
Але не варто робити ставку на те, що якийсь із них є "більш ефективним". gcc фактично генерує менше коду, x == 0 || x == 1ніж для (x & ~1) == 0або (x | 1) == 1. Для першого він досить розумний, щоб визнати його рівноцінним x <= 1та виводить простим cmpl; setbe. Інші плутають його і змушують генерувати гірший код.
варення

13
x <= 1 або x <2 простіше.
gnasher729

9
@Kevin True для C ++, оскільки цей стандарт намагається дуже, дуже важко унеможливити писати сумісний код. На щастя, це питання про C #;)
Voo

5
Більшість сучасних компіляторів уже можуть оптимізувати подібні порівняння, хоча я не знаю, наскільки розумні компілятор C # та .NET JITter. У реальному коді
потрібне

78

Оскільки аргумент є 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);

12
Якби це був Code Golf, це було б 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);

Для повноти завдяки Метту ще краще рішення:

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

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


Мені це подобається! Дякую.
user6048670,

15
На 1 персонаж менше:(N|1)==1
Метт

1
@atk 3 | 1 є 3, тому що b0011 | b0001 є b0011
Рене Фогт

3
@atk Це побіжно або, не логічно або. Не існує короткочасного керування.
isaacg

2
@Hoten Правильно, але Метт сказав на 1 менший символ , не на 1 меншу операцію .
Іван Стоєв

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

Як це зробити за допомогою бітшифта

Якщо ви хочете скористатися bitshift і зробити код дещо незрозумілим (але коротким), ви можете зробити:

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

Для цілого числа без підпису Nна мові c N>>1скидає біт низького порядку. Якщо цей результат не дорівнює нулю, це означає, що N більший за 1.

Примітка: цей алгоритм жахливо неефективний, оскільки він зайвий перерахунок значень у послідовності, яка вже була обчислена.

Щось ШЛЯХ БЕШЕ

Обчисліть його одним проходом, а не неявним побудовою дерева з розміром фібонаків (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
Ваш код "Швидше швидше" не працюватиме в C #, оскільки uintце не піддається неявному обговоренню bool, і питання спеціально позначено як C #.
Фарап

1
@pharap тоді зробіть --N != 0замість цього. Справа в тому, що щось O (n) є кращим перед O (fibn (n)).
Меттью Ганн

1
щоб розгорнутись на точку @ MatthewGunn, O (fib (n)) є O (phi ^ n) (див. це походження stackoverflow.com/a/360773/2788187 )
Коннор Кларк

@ RenéVogt Я не розробник ac #. Я в основному намагався прокоментувати повну абсурдність алгоритму O (fibn (N)). Чи складається вона зараз? (Я додав! = 0, оскільки c # не розглядає ненульові результати як істинні.) Він працює (і працює) у прямому c, якщо ви заміните uint чимось стандартним, наприклад, uint64_t.
Меттью Ганн

10

Використовуючи утинку, яка не може отримати негатив, ви можете перевірити, чи немає n < 2

EDIT

Або для цього випадку спеціальної функції ви можете написати це так:

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

що призведе до того ж результату, звичайно ціною додаткового кроку рекурсії.


4
@CatthalMF: але результат той самий, тому що1 * fibn(0) = 1 * 1 = 1
derpirscher

3
Чи не ваша функція обчислення факторних, не полевих?
Бармар

2
@Barmar так, це справді фактично, тому що це було первісне питання
derpirscher

3
Найкраще тоді не називати fibnцього
pie3636

1
@ pie3636 Я назвав це fibn, тому що так воно називалося в оригінальному запитанні, і я не оновив відповідь пізніше
derpirscher

6

Просто перевірте, чи Nє <= 1, оскільки ви знаєте, що N не підписано, може бути лише 2 умови, N <= 1що призводять до TRUE: 0 і 1

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

Чи має значення навіть те, підписаний він чи без підпису? Алгоритм створює нескінченну рекурсію з негативними входами, тому немає ніякої шкоди в їх обробці, рівнозначній 0 або 1.
Barmar

@Barmar впевнений, що це має значення, особливо в цьому конкретному випадку. ОП запитав, чи може він спростити точно (N == 0 || N == 1). Ви знаєте, що він не буде меншим за 0 (адже тоді це було б підписано!), А максимум може бути 1. N <= 1спрощує це. Я думаю, непідписаний тип не гарантується, але це слід вирішити в іншому місці, я б сказав.
Джеймс

Моя думка полягає в тому, що якби це було оголошено int N, і ви зберегли первісну умову, воно буде повторюватися нескінченно, коли N має негативний характер зі своїм початковим станом. Оскільки це невизначена поведінка, нам насправді не потрібно турбуватися про це. Тож можна припустити, що N - негативний, незалежно від декларації.
Бармар

Або ми можемо робити все, що завгодно, з негативними вхідними даними, включаючи трактування їх як основного випадку рекурсії.
Бармар

@Barmar досить впевнений, що uint завжди буде перетворений на безпідписаний, якщо ви спробуєте встановити негатив
Джеймс

6

Відмова: Я не знаю C # і не перевіряв цей код:

Але мені цікаво, чи можу я зробити це ще більш компактним та ефективним, змінивши [...] в єдине порівняння ...

Немає потреби в бітшифтингу чи подібному, для цього використовується лише одне порівняння, і воно повинно бути набагато ефективнішим (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ви не ефективно використовувати не мутації, що робить його придатним для використання в чистому функціональному підході.


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 Я додав достатньо значень для комутатора, щоб створити для нього таблицю пошуку. Я не впевнений у тому, як працює альтернативний бонус.
Khaled.K

1
Як це відповідає на питання?
Кларк Кент

@SaviourSelf зводиться до таблиці пошуку, і у відповіді є візуальний аспект. stackoverflow.com/a/395965/2128327
Khaled.K

Чому б ви використовували switch коли у вас є масив відповідей?
Наюкі


1

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

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

2
Чим це коротше оригіналу?
MCMastery

2
@MCMastery Його не коротше. Як я вже згадував, краще лише, якщо вихідний тип повернення - це int32 і він вибирає з великого набору дійсних чисел. Замість того, щоб писати (N == -1 || N == 0 || N == 1 || N == 2)
CathalMF

1
Причини ОП, схоже, пов'язані з оптимізацією. Це погана ідея з кількох причин: 1) інстанціювання нового об'єкта всередині кожного рекурсивного виклику - це дійсно погана ідея; 2) List.ContainsO (n), 3) просто проведення двох порівнянь замість ( N > -3 && N < 3) дасть коротший і читабельніший код.
Груо

@Groo І що, якщо значення були -10, -2, 5, 7, 13
CathalMF

Це не те, що запитувала ОП. Але все одно, ви все-таки 1) не хотіли б створювати новий примірник у кожному виклику; 2) краще використовувати (один) хеш-пакет замість цього 3) для конкретної проблеми, ви також зможете оптимізувати хеш-функцію для бути чистим або навіть використовувати розумно розташовані побітні операції, як запропоновано в інших відповідях.
Груо

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 Expression-body та пропозицію Дмитра про потрійний оператор, ми можемо записати функцію в одному рядку з правильним обчисленням для типу 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)

!!xперетворює значення, 1якщо воно немає 0, і залишає його, 0якщо воно є.
Я дуже часто користуюся цією річчю в обтурації С.

Примітка. Це C, не впевнений, чи працює він у C #


4
Не впевнений, чому це було сприйнято. Навіть побіжний спробує це, як uint n = 1; if (n == !!n) { }дає Operator '!' cannot be applied to operand of type 'uint'на !nC #. Просто те, що щось працює в C, не означає, що воно працює в C #; навіть #include <stdio.h>не працює в C #, оскільки C # не має директиви "включити" препроцесора. Мови сильніше відрізняються, ніж C та C ++.
CVn

2
Ой. Добре. Вибачте :(
Один звичайний вечір

@OneNormalNight (x == !! x) Як це буде працювати. Вважайте, що мій внесок дорівнює 5. (5 == !! 5). Це дасть результат як справжній
VINOTH ENERGETIC

1
@VinothKumar !! 5 оцінює до 1. (5 == !! 5) оцінює (5 == 1), що оцінює як хибне.
Одна нормальна ніч

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

Це може бути не найшвидший спосіб зробити це, але мені здається, що це кращий стиль.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.