web-dev-qa-db-fra.com

Est-il possible de simplifier (x == 0 || x == 1) en une seule opération?

J'essayais donc d'écrire le n ème nombre de la suite de Fibonacci dans une fonction aussi compacte que possible:

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

Mais je me demande si je peux rendre cela encore plus compact et efficace en changeant

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

en une seule comparaison. Y at-il une opération de décalage de bit de fantaisie qui peut faire cela?

106
user6048670

Il existe plusieurs façons d'implémenter votre test arithmétique à l'aide de l'arithmétique au niveau des bits. Votre expression:

  • x == 0 || x == 1

est logiquement équivalent à chacun de ceux-ci:

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

Prime:

  • x * x == x _ (la preuve demande un peu d'effort)

Mais dans la pratique, ces formes sont les plus lisibles et la petite différence de performances ne vaut pas la peine d’utiliser l’arithmétique au niveau des bits:

  • x == 0 || x == 1
  • x <= 1 (parce que x est un entier non signé)
  • x < 2 (parce que x est un entier non signé)
209
Nayuki

Puisque l’argument est uint ( unsigned ), vous pouvez mettre

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

Moins lisible (IMHO) mais si vous comptez chaque caractère ( Code Golf ou similaire)

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

Edit: pour votre question modifiée :

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

Ou

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

Vous pouvez également vérifier que tous les autres bits sont 0 comme ceci:

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

Pour être complet grâce à Matt la meilleure solution:

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

Dans les deux cas, vous devez prendre soin de la parenthèse car les opérateurs au niveau du bit ont une priorité inférieure à celle de ==.

36
René Vogt

Si vous voulez rendre la fonction plus efficace, utilisez une table de correspondance. La table de recherche est étonnamment petite, avec seulement 47 entrées. L'entrée suivante déborderait d'un nombre entier non signé de 32 bits. Bien entendu, cela rend également la fonction triviale à écrire.

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

Vous pouvez évidemment faire la même chose pour les factorielles.

20
Adam

Comment le faire avec bitshift

Si vous voulez utiliser bitshift et rendre le code un peu obscur (mais bref), vous pouvez faire:

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

Pour un entier non signé N dans la langue c, N>>1 jette le bit de poids faible. Si ce résultat est non nul, cela signifie que N est supérieur à 1.

Remarque: cet algorithme est terriblement inefficace car il recalcule inutilement les valeurs de la séquence déjà calculées.

Quelque chose de beaucoup plus rapide

Calculez-le en un seul passage plutôt que de construire implicitement un arbre de taille 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;
}

Comme certains l’ont mentionné, le débordement ne prend pas longtemps, même pour un entier non signé de 64 bits. En fonction de votre taille, vous devrez utiliser des entiers à précision arbitraire.

14
Matthew Gunn

Comme vous utilisez un uint, qui ne peut pas être négatif, vous pouvez vérifier si n < 2

MODIFIER

Ou pour ce cas de fonction spécial, vous pouvez l'écrire comme suit:

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

qui aboutira au même résultat, bien entendu au prix d’une étape supplémentaire de récursivité.

10
derpirscher

Vérifiez simplement si N est <= 1 puisque vous savez que N n’est pas signé, il ne peut y avoir que 2 conditions pour que N <= 1 qui donne TRUE: 0 et 1

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

Avertissement: je ne connais pas C # et je n'ai pas testé ce code:

Mais je me demande si je peux rendre cela encore plus compact et efficace en convertissant [...] en une seule comparaison ...

Pas besoin de bitshifting ou autre, cela utilise une seule comparaison, et cela devrait être beaucoup plus efficace (O(n) vs O (2 ^ n ) Je pense?). Le corps de la fonction est plus compact, même s’il finit par être un peu plus long avec la déclaration.

(Pour supprimer la surcharge de la récursion, il existe la version itérative, comme dans réponse de 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: Il s'agit d'un modèle fonctionnel commun pour l'itération avec des accumulateurs. Si vous remplacez N-- avec N-1 vous n’utilisez effectivement aucune mutation, ce qui la rend utilisable dans une approche purement fonctionnelle.

6
fede s.

Voici ma solution, l'optimisation de cette fonction simple n'a pas grand-chose. Par contre, ce que je propose ici, c'est la lisibilité en tant que définition mathématique de la fonction récursive.

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

        case  1: return 1;

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

La définition mathématique du nombre de Fibonacci de la même manière.

enter image description here

Aller plus loin pour forcer le boîtier de commutateur à créer une table de consultation.

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);
    }
}
4
Khaled.K

pour N est uint, il suffit d'utiliser

N <= 1
3
yanghaogn

La réponse de Dmitry est préférable, mais vous pouvez le faire s'il s'agit d'un type de retour Int32 et que vous avez un plus grand ensemble d'entiers.

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

La séquence de Fibonacci est une série de nombres dans lesquels on trouve un nombre en additionnant les deux chiffres qui le précèdent. Il existe deux types de points de départ: ( 0,1 , 1,2, ..) et ( 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
-----------------------------------------

La position N dans ce cas commence à partir de 1, ce n'est pas 0-based sous forme d’index de tableau.

En utilisant fonctionnalité C-6 Expression-body et la suggestion de Dmitry concernant opérateur ternaire , nous pouvons écrire une fonction sur une ligne avec un calcul correct pour le type 1:

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

et pour le type 2:

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