Trouver la position d'une fraction dans l'arbre Stern-Brocot


11

L' arbre de Stern-Brocot est un arbre binaire de fractions où chaque fraction est acquise en ajoutant les numérateurs et les dénominateurs des deux fractions voisines dans les niveaux ci-dessus.

Il est généré en commençant par 0/1et en 1/0tant que "fractions de point final", et à partir de là, en itérant en plaçant une fraction entre chaque paire de fractions consécutives en ajoutant les numérateurs et les dénominateurs de ces fractions, comme ceci:

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

Dans chaque itération de l'arbre Stern-Brocot (la nième itération), il y a des 2^n + 1éléments dans la séquence, auxquels nous pouvons attribuer une fraction de 0/2^nà 2^n/2^n. Chaque nouvelle itération insère simplement une fraction "à mi-chemin" entre chaque paire de fractions consécutives.

Cela fait de l'arbre de Stern-Brocot une correspondance biunivoque entre les nombres rationnels positifs et les fractions binaires entre 0 et 1, ce qui sert également de preuve que les deux ensembles ont la même cardinalité.

Votre tâche consiste à écrire un programme ou une fonction qui, étant donné le numérateur et le dénominateur d'un nombre rationnel positif en termes les plus bas, détermine la fraction binaire qui correspond à la position de cette fraction dans l'arbre Stern-Brocot.

Des exemples d'entrées et de sorties sont fournis ci-dessous:

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)

Entrées que vous n'avez pas besoin de prendre en charge, mais qui sont incluses pour référence:

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

Le programme le plus court dans n'importe quelle langue pour atteindre cet objectif gagne.


Y a-t-il des exigences d'entrée / sortie? Par exemple, une fonction suffit-elle comme dans votre solution de référence, ou doit-elle être un programme autonome? Le format de sortie des fractions est-il important?
Darren Stone

Une fonction suffit. Je vais clarifier cela dans la description du problème.
Joe Z.

Il est un peu tard pour moi d'y penser; J'essaierai probablement de le clarifier demain.
Joe Z.

2
Ok, je pense que la bijection que vous avez en tête est d'attribuer à chaque profondeur de l'arbre un dénominateur constant 2 ^ (profondeur + 1) et les numérateurs 1, 3, 5, 7, ... à partir de la gauche.
Peter Taylor

1
Une autre façon de le construire est d'abord numéroter les noeuds de l'arbre en largeur d'abord à partir de 1 (c. -à- 1/1 => 1, 1/2 => 2, 2/1 => 3, 1/3 => 4, etc.). Si le nombre ainsi généré pour un nœud est n, alors 2^lg n(journal binaire) est le bit le plus élevé défini net la fraction binaire souhaitée est (2*(n - 2^lg n) + 1) / 2^(lg n + 1). (Quiconque tente une solution d'assembleur dans un jeu d'instructions avec un bit de jeu le plus élevé voudra probablement utiliser cette approche).
Peter Taylor

Réponses:


1

GolfScript ( 49 48 46 caractères)

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

ou

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

Les deux sont des fonctions qui prennent le numérateur et le dénominateur sur la pile et laissent le numérateur et le dénominateur sur la pile. Démo en ligne .

L'idée principale est exprimée en pseudocode dans la section 4.5 de Concrete Mathematics (p122 dans mon édition):

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

Si la chaîne de Ls et Rs est interprétée comme une valeur binaire avec L = 0 et R = 1, deux fois cette valeur plus un est le numérateur et le dénominateur est un peu plus long.

Comme point d'intérêt pour Golfscripters, c'est l'une de ces rares occasions où j'ai trouvé déplier utile. (Ok, je ne l'utilise que comme compteur de boucles, mais c'est mieux que rien).


1

Mathematica, 130 114 111 111 caractères

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

Exemple:

f[2/3]

3/8

f[4/7]

9/32

f[1]

1/2


1

Rubis, 132 125

Rubied & Golfed la solution de référence de @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

Exemples d'utilisation:

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

1

Ruby (69 caractères) CoffeeScript (59 caractères)

Il s'agit d'une fonction qui prend le numérateur et le dénominateur comme arguments et renvoie un tableau contenant le numérateur et le dénominateur après la bijection.

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]

Démo en ligne

Il utilise la même approche que ma solution GolfScript ci-dessus, mais est beaucoup plus lisible car je peux utiliser 4 variables sans avoir à se soucier de la mise en boîte et du décompression dans un tableau. J'ai choisi CoffeeScript car il ne préfixe pas les variables $(20 caractères enregistrés sur PHP, par exemple), a une syntaxe de définition de fonction courte qui autorise les valeurs de paramètre par défaut (il n'est donc pas nécessaire d'envelopper f(a,b,x,y)une fonction g(a,b) = f(a,b,0,1)), et me permet d'utiliser des booléens comme entiers dans expressions avec des valeurs utiles. Pour ceux qui ne le connaissent pas, CoffeeScript n'a pas l'opérateur ternaire standard de style C ( C?P:Q), mais je peux le remplacer C&&P||Qici car Pil ne sera jamais faux.

Une alternative sans doute plus élégante, mais sans doute moins courte, est de remplacer la soustraction répétée par la division et le modulo:

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 caractères; démo en ligne ). L'écrire de cette façon expose la relation avec l'algorithme d'Euclide.


Vous n'avez pas besoin des parenthèses a<bqui vous font économiser un caractère. L'inscription en cdonne deux autres. Vous pouvez également considérer la syntaxe f=->a,b,x=0,y=1{...}pour une définition encore plus courte.
Howard

@Howard, je ne sais pas quelle version de Ruby vous utilisez, mais sur IDEOne j'obtiens une erreur de syntaxe si j'essaie de supprimer ces parenthèses ou d'utiliser cette syntaxe de fonction.
Peter Taylor

Essayez c=a<b ?avec un espace supplémentaire après b. Sinon, le point d'interrogation est traité comme faisant partie du b.
Howard

0

Python - 531

Une solution non golfée en Python, pour servir de solution de référence en dernier lieu:

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

Il effectue simplement une recherche binaire entre les fractions, profitant du fait que le médian de deux fractions sera toujours entre les valeurs de ces deux fractions.


0

GolfScript, 54 caractères

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

L'entrée doit être donnée sur STDIN sous la forme spécifiée dans la tâche. Vous pouvez essayer le code en ligne .

> 4/7
9/32

> 9/7
35/64

> 5/1
31/32

0

Mathematica 138

Pas aussi rationalisé que la procédure d'alephalpha, mais c'était le meilleur que j'ai pu produire jusqu'à présent.

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]

Essai

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

3/8
9/32
1/2


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

pourrait être moins, mais j'aime le golf lisible


0

Haskell , 125 octets

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

Essayez-le en ligne!

Entrée et sortie sous forme de paire (n,d).

Brève explication:

nconstruit la ligne suivante à partir de la précédente en regardant chaque paire de fractions et en insérant la nouvelle entre la première et la récursion (ce qui placera la deuxième fraction juste là). Le cas de base est très simple car il s'agit essentiellement de la fonction d'identité. La tfonction itère cette fonction indéfiniment en fonction de l'état initial avec uniquement les deux fractions de frontière. tindexe ensuite chaque ligne ( i) et chaque élément de la ligne ( j) et recherche la première fraction qui correspond à ce que nous recherchons. Quand il le trouve, il donne jcomme numérateur et 2^icomme dénominateur.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.