Encontre a posição de uma fração na árvore Stern-Brocot

11

A árvore Stern-Brocot é uma árvore binária de frações, onde cada fração é adquirida adicionando os numeradores e denominadores das duas frações vizinhas nos níveis acima.

É gerado iniciando com 0/1e 1/0como "frações de ponto final" e, a partir daí, iterando colocando uma fração entre cada par consecutivo de frações adicionando os numeradores e denominadores dessas frações, da seguinte forma:

0.  0/1                                                             1/0
1.  0/1                             1/1                             1/0
2.  0/1             1/2             1/1             2/1             1/0
3.  0/1     1/3     1/2     2/3     1/1     3/2     2/1     3/1     1/0
4.  0/1 1/4 1/3 2/5 1/2 3/5 2/3 3/4 1/1 4/3 3/2 5/3 2/1 5/2 3/1 4/1 1/0

Em cada iteração da árvore Stern-Brocot (a niteração), existem 2^n + 1elementos na sequência aos quais podemos atribuir uma fração de 0/2^npara 2^n/2^n. Cada nova iteração simplesmente insere uma fração "na metade" entre cada par de frações consecutivas.

Isso faz da árvore de Stern-Brocot um mapeamento individual entre os números racionais positivos e as frações binárias entre 0 e 1, servindo também como prova de que os dois conjuntos têm a mesma cardinalidade.

Sua tarefa é escrever um programa ou função que, dado o numerador e o denominador de um número racional positivo em termos mais baixos, determine a fração binária que corresponde à posição dessa fração na árvore Stern-Brocot.

Exemplos de entradas e saídas são fornecidos abaixo:

2/3 -> 3/8   (4th number in iteration 3)
4/7 -> 9/32  (between 1/2 and 3/5 in the chart above)
1/1 -> 1/2   (middle number in the first iteration)

Entradas que você não precisa oferecer suporte, mas estão incluídas para referência:

0/1 -> 0/1   (0/1 is considered the left number)
1/0 -> 1/1   (1/0 is considered the rightmost number)

O programa mais curto em qualquer idioma para atingir esse objetivo vence.

Joe Z.
fonte
Existem requisitos de entrada / saída? Por exemplo, apenas uma função é suficiente como na sua solução de referência ou precisa ser um programa independente? O formato da saída da fração é importante?
precisa
Uma função é suficiente. Vou deixar isso mais claro na descrição do problema.
Joe Z.
É um pouco tarde para eu pensar nisso; Provavelmente vou tentar esclarecer amanhã.
Joe Z.
2
Ok, acho que a idéia que você tem em mente é atribuir a cada profundidade da árvore um denominador constante 2 ^ (profundidade + 1) e numeradores 1, 3, 5, 7, ... da esquerda.
Peter Taylor
1
Uma forma alternativa de construção é a primeira série os nós da árvore no fim de partida em largura em 1 (isto é 1/1 => 1, 1/2 => 2, 2/1 => 3, 1/3 => 4, etc.). Se o número assim gerado para um nó for n, então 2^lg n(log binário) será o bit mais alto configurado ne a fração binária desejada será (2*(n - 2^lg n) + 1) / 2^(lg n + 1). (Qualquer pessoa que tente uma solução de assembler em um conjunto de instruções com um bit de maior valor definido provavelmente desejará usar essa abordagem).
Peter Taylor

Respostas:

1

GolfScript ( 49 48 46 caracteres)

{0\@{}{@2*2$2$>!+@@{{\}3$)*}:j~1$-j}/\)\,?}:f;

ou

{0:x;\{}{.2$<!2x*+:x){\}*1$-{\}x)*}/x@)@,?}:g;

Ambas são funções que pegam o numerador e o denominador na pilha e deixam o numerador e o denominador na pilha. Demonstração online .

A idéia central é expressa em pseudocódigo na seção Concrete Mathematics 4.5 (p122 em minha edição):

while m != n do
    if m < n then (output(L); n := n - m)
             else (output(R); m := m - n)

Se a sequência de Ls e Rs é interpretada como um valor binário com L = 0 e R = 1, o dobro desse valor mais um é o numerador e o denominador é um pouco mais longo.

Como um ponto de interesse para os Golfscripters, essa é uma daquelas raras ocasiões em que considero útil o desdobramento. (Ok, eu apenas o uso como contador de loop, mas é melhor que nada).

Peter Taylor
fonte
1

Mathematica, 130 114 111 caracteres

f=#~g~0&;0~g~q_=q;p_~g~q_:=g[#,(Sign[p-#]+q)/2]&@FromContinuedFraction[ContinuedFraction@p/.{x___,n_}:>{x,n-1}]

Exemplo:

f[2/3]

3/8

f[4/7]

9/32

f[1]

1/2

alefalpha
fonte
1

Ruby, 132 125

Rubied & golfed a solução de referência da @JoeZ.

def t(n,d)u=k=0;v,j,f,g,b=[1,]*5;c=2
while(z=(f*d).<=>(g*n))!=0;z>0?(j,k=f,g):(u,v=f,g);b=b*2-z;f,g=u+j,v+k;c*=2;end
[b,c]end

Exemplos de uso:

>> t(2,3)
=> [3, 8]
>> t(4,7)
=> [9, 32]
>> t(1,1)
=> [1, 2]
Darren Stone
fonte
1

Ruby (69 caracteres) CoffeeScript (59 caracteres)

Esta é uma função que recebe numerador e denominador como argumentos e retorna uma matriz que contém o numerador e o denominador após a bijeção.

g=(a,b,x=0,y=1)->c=a>=b;a&&g(a-b*c,b-a*!c,2*x+c,2*y)||[x,y]

Demonstração online

Ele usa a mesma abordagem da minha solução GolfScript acima, mas é muito mais legível, porque eu posso usar 4 variáveis ​​sem ter que me preocupar com boxe e unboxing em uma matriz. Eu escolhi o CoffeeScript porque ele não prefixa variáveis ​​com $(20 caracteres salvos, por exemplo, PHP), possui uma sintaxe curta de definição de função que permite valores de parâmetro padrão (portanto, não há necessidade de agrupar f(a,b,x,y)uma função g(a,b) = f(a,b,0,1)) e me permite usar Booleans como números inteiros em expressões com valores úteis. Para quem não conhece, o CoffeeScript não possui o operador ternário padrão do estilo C ( C?P:Q), mas posso substituí-lo C&&P||Qaqui, porque Pnunca será falso.

Uma alternativa sem dúvida mais elegante, mas indiscutivelmente menos curta, é substituir a subtração repetida por divisão e módulo:

f=(a,b,x=0,y=1,p=0)->a&&f(b%a,a,(x+p<<b/a)-p,y<<b/a,1-p)||[x+p,y]

(65 caracteres; demonstração online ). Escrever dessa maneira expõe o relacionamento com o algoritmo de Euclides.

Peter Taylor
fonte
Você não precisa dos parênteses em torno dos a<bquais economiza um caractere. Inlining cdá outros dois. Você também pode considerar a sintaxe f=->a,b,x=0,y=1{...}para uma definição ainda mais curta.
Howard
@Howard, não sei qual versão do Ruby você está usando, mas no IDEOne recebo um erro de sintaxe se tentar remover esses parênteses ou usar a sintaxe da função.
Peter Taylor
Tente c=a<b ?com um espaço extra depois b. Caso contrário, o ponto de interrogação é tratado como parte do b.
Howard
0

Python - 531

Uma solução não destruída em Python, para servir como uma solução de referência de último lugar:

def sbtree(n, d): 
    ufrac = [0, 1]
    lfrac = [1, 0]
    frac = [1, 1]
    bfrac = [1, 2]
    while(frac[0] * d != frac[1] * n): 
        if(frac[0] * d > frac[1] * n): 
            # push it towards lfrac
            lfrac[0] = frac[0]
            lfrac[1] = frac[1]
            bfrac[0] = bfrac[0] * 2 - 1 
        elif(frac[0] * d < frac[1] * n): 
            # push it towards ufrac
            ufrac[0] = frac[0]
            ufrac[1] = frac[1]
            bfrac[0] = bfrac[0] * 2 + 1 
        frac[0] = ufrac[0] + lfrac[0]
        frac[1] = ufrac[1] + lfrac[1]
        bfrac[1] *= 2
    return bfrac

Ele simplesmente faz uma pesquisa binária entre frações, aproveitando o fato de que a mediana de quaisquer duas frações sempre estará entre os valores dessas duas frações.

Joe Z.
fonte
0

GolfScript, 54 caracteres

'/'/n*~][2,.-1%]{[{.~3$~@+@@+[\]\}*].2$?0<}do.@?'/'@,(

A entrada deve ser fornecida no STDIN no formato especificado na tarefa. Você pode tentar o código online .

> 4/7
9/32

> 9/7
35/64

> 5/1
31/32
Howard
fonte
0

Mathematica 138

Não é tão simples quanto o procedimento de alefalpha, mas foi o melhor que pude produzir até agora.

q_~r~k_:=Nest[#+Sign@k/(2Denominator@# )&,q,Abs@k]  
g@d_:=
Module[{l=ContinuedFraction@d,p=-1},
l[[-1]]-=1;
(p=-p;# p)&/@l]
h[q_]:=Fold[r,1/2,g@q]

Teste

h[2/3]
h[4/7]
h[1]

3/8
9/32
1/2

DavidC
fonte
0

JavaScript 186

f=(p1,q1,p2,q2)=>{if(p1*q2+1==p2*q1){return{p:p1+p2,q:q1+q2}}let p,q,pl=0,ql=1,ph=1,qh=0;for(;;){p=pl+ph;q=ql+qh;if(p*q1<=q*p1){pl=p;ql=q}else if(p2*q<=q2*p){ph=p;qh=q}else return{p,q}}}

poderia ser menor, mas eu gosto de golfe legível

caub
fonte
0

Haskell , 125 bytes

n((a,b):(c,d):r)=(a,b):(a+c,b+d):n((c,d):r)
n a=a
z=zip[0..]
t x=[(j,2^i)|(i,r)<-z$iterate n[(0,1),(1,0)],(j,y)<-z r,x==y]!!0

Experimente online!

Entrada e saída na forma de um par (n,d).

Breve explicação:

nconstrói a próxima linha da anterior, olhando para cada par de frações e inserindo a nova entre a primeira e a recursão (que colocará a segunda fração ali). O caso base é muito simples, pois é basicamente apenas a função de identidade. A tfunção itera essa função indefinidamente com base no estado inicial com apenas as duas frações de limite. tdepois indexa cada linha ( i) e cada item da linha ( j) e procura a primeira fração que corresponde ao que estamos procurando. Quando encontra, cede jcomo numerador e 2^icomo denominador.

user1472751
fonte