Então, eu estava tentando escrever o n º número na seqüência de Fibonacci em como a função de um compacto quanto possível:
public uint fibn ( uint N )
{
return (N == 0 || N == 1) ? 1 : fibn(N-1) + fibn(N-2);
}
Mas estou me perguntando se posso tornar isso ainda mais compacto e eficiente mudando
(N == 0 || N == 1)
em uma única comparação. Existe alguma operação de mudança de bit sofisticada que pode fazer isso?
c#
algorithm
optimization
arithmetic-expressions
user6048670
fonte
fonte
fibn(N-1) + fibn(N-2)
vez deN * fibn(N-1)
?Respostas:
Este também funciona
a raiz quadrada de 0 e 1 retornará 0 e 1, respectivamente.
fonte
Math.Sqrt
é uma função de ponto flutuante complicada. Ele roda lentamente em comparação com as alternativas somente inteiras !!Existem várias maneiras de implementar seu teste aritmético usando aritmética bit a bit. Sua expressão:
x == 0 || x == 1
é logicamente equivalente a cada um destes:
(x & 1) == x
(x & ~1) == 0
(x | 1) == 1
(~x | 1) == (uint)-1
x >> 1 == 0
Bônus:
x * x == x
(a prova exige um pouco de esforço)Mas, falando de forma prática, esses formulários são os mais legíveis, e a pequena diferença no desempenho não vale o uso da aritmética bit a bit:
x == 0 || x == 1
x <= 1
(porquex
é um inteiro sem sinal)x < 2
(porquex
é um inteiro sem sinal)fonte
(x & ~1) == 0
x == 0 || x == 1
que para(x & ~1) == 0
ou(x | 1) == 1
. Para o primeiro, é inteligente o suficiente para reconhecê-lo como equivalente ax <= 1
e resulta em um simplescmpl; setbe
. Os outros o confundem e fazem com que ele gere um código pior.Uma vez que o argumento é
uint
( sem sinal ), você pode colocarMenos legível (IMHO), mas se você contar cada personagem ( Code Golf ou similar)
Editar : para sua pergunta editada :
Ou
fonte
return N<2?1:f(N-1)+f(n-2)
. : PVocê também pode verificar se todos os outros bits são 0, como este:
Para completar, graças a Matt, a solução ainda melhor:
Em ambos os casos, você precisa cuidar dos parênteses porque os operadores bit a bit têm prioridade menor do que
==
.fonte
(N|1)==1
Se o que você deseja fazer é tornar a função mais eficiente, use uma tabela de pesquisa. A tabela de pesquisa é surpreendentemente pequena com apenas 47 entradas - a próxima entrada estouraria um inteiro não assinado de 32 bits. É claro que também torna a função trivial de escrever.
Obviamente, você pode fazer a mesma coisa para fatoriais.
fonte
Como fazer isso com bitshift
Se você quiser usar bitshift e tornar o código um tanto obscuro (mas curto), você pode fazer:
Para um inteiro sem sinal
N
na linguagem c,N>>1
descarta o bit de ordem inferior. Se esse resultado for diferente de zero, isso implica que N é maior que 1.Nota: este algoritmo é terrivelmente ineficiente, pois recalcula desnecessariamente os valores na sequência que já foram calculados.
Algo BEM MUITO mais rápido
Calcule uma passagem, em vez de construir implicitamente uma árvore do tamanho de fibonaci (N):
Como algumas pessoas mencionaram, não demora muito para estourar até mesmo um número inteiro sem sinal de 64 bits. Dependendo de quão grande você está tentando ir, você precisará usar inteiros de precisão arbitrária.
fonte
uint
não pode ser convertido implicitamente embool
, e a pergunta está especificamente marcada como C #.--N != 0
. A questão é que algo O (n) é preferível a O (fibn (n)).Ao usar um uint, que não pode ser negativo, você pode verificar se
n < 2
EDITAR
Ou para esse caso de função especial, você poderia escrevê-lo da seguinte forma:
que levará ao mesmo resultado, é claro, ao custo de uma etapa de recursão adicional.
fonte
1 * fibn(0) = 1 * 1 = 1
fibn
entãoBasta verificar se
N
é <= 1, pois você sabe que N não tem sinal, só pode haver 2 condiçõesN <= 1
que resultam emTRUE
: 0 e 1fonte
(N == 0 || N == 1)
. Você sabe que não será menor que 0 (porque então estaria assinado!), E o máximo poderia ser 1.N <= 1
simplifica. Acho que o tipo sem sinal não é garantido, mas isso deve ser tratado em outro lugar, eu diria.int N
, e você mantivesse a condição original, ele ocorreria infinitamente quando N fosse negativo com sua condição original. Como esse é um comportamento indefinido, não precisamos nos preocupar com isso. Portanto, podemos supor que N é não negativo, independentemente da declaração.Isenção de responsabilidade: não conheço C # e não testei este código:
Não há necessidade de bitshifting ou algo semelhante, ele usa apenas uma comparação e deve ser muito mais eficiente (O (n) vs O (2 ^ n), eu acho?). O corpo da função é mais compacto , embora acabe sendo um pouco mais longo com a declaração.
(Para remover a sobrecarga da recursão, existe a versão iterativa, como na resposta de Mathew Gunn )
PS: Este é um padrão funcional comum para iteração com acumuladores. Se você substituir
N--
por,N-1
estará efetivamente usando nenhuma mutação, o que o torna utilizável em uma abordagem puramente funcional.fonte
Esta é minha solução, não há muito em otimizar esta função simples, por outro lado, o que estou oferecendo aqui é a legibilidade como uma definição matemática da função recursiva.
A definição matemática do número de Fibonacci de forma semelhante.
Levando isso adiante para forçar o caso de switch a construir uma tabela de pesquisa.
fonte
switch
quando pode ter uma variedade de respostas?para N é uint, basta usar
fonte
A resposta de Dmitry é melhor, mas se fosse um tipo de retorno Int32 e você tivesse um conjunto maior de números inteiros para escolher, você poderia fazer isso.
fonte
List.Contains
é O (n), 3) simplesmente fazer duas comparações (N > -3 && N < 3
) daria um código mais curto e legível.A seqüência de Fibonacci é uma série de números onde um número é encontrado somando os dois números anteriores. Existem dois tipos de pontos de partida: ( 0,1 , 1,2, ..) e ( 1,1 , 2,3).
A posição
N
, neste caso, começa de1
, não é0-based
como um índice de array.Usando o recurso Expression-body do C # 6 e a sugestão de Dmitry sobre o operador ternário , podemos escrever uma função de uma linha com cálculo correto para o tipo 1:
e para o tipo 2:
fonte
Um pouco tarde para a festa, mas você também pode fazer
(x==!!x)
!!x
converte o valor a em1
se não for0
e o deixa em0
se for.Eu uso muito esse tipo de ofuscação.
Nota: Este é C, não tenho certeza se funciona em C #
fonte
uint n = 1; if (n == !!n) { }
dáOperator '!' cannot be applied to operand of type 'uint'
no!n
em C #. Só porque algo funciona em C não significa que funcione em C #; nem mesmo#include <stdio.h>
funciona em C #, porque C # não tem a diretiva de pré-processador "include". As linguagens são mais diferentes do que C e C ++.Então criei um
List
desses inteiros especiais e verifiquei seN
pertence a ele.Você também pode usar um método de extensão para finalidades diferentes onde
Contains
é chamado apenas uma vez (por exemplo, quando seu aplicativo está iniciando e carregando dados). Isso fornece um estilo mais claro e esclarece a relação principal com seu valor (N
):Aplicam-na:
Esta pode não ser a maneira mais rápida de fazer isso, mas para mim, parece ser um estilo melhor.
fonte