Znajdź pozycję ułamka w drzewie Sterna-Brocota

11

Drzewo Sterna-Brocota jest drzewo binarne, w którym każdy z frakcji frakcji uzyskuje się przez dodanie licznik i mianownik dwóch frakcji sąsiednich ją w ilości powyżej.

Jest generowany przez rozpoczynanie od 0/1i 1/0jako „ułamki punktu końcowego”, a następnie iterowanie przez umieszczenie jednej frakcji między każdą kolejną parą ułamków przez dodanie razem liczników i mianowników tych ułamków, w następujący sposób:

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

W każdej iteracji drzewa Sterna-Brocota ( niteracja) 2^n + 1w sekwencji znajdują się elementy, którym możemy przypisać ułamek od 0/2^ndo 2^n/2^n. Każda nowa iteracja po prostu wstawia jedną frakcję „w połowie” między każdą parą kolejnych frakcji.

To sprawia, że ​​drzewo Sterna-Brocota jest mapowaniem jeden na jeden między dodatnimi liczbami wymiernymi a ułamkami binarnymi między 0 a 1, tym samym służąc również jako dowód, że oba zestawy mają tę samą liczność.

Twoim zadaniem jest napisanie programu lub funkcji, która, biorąc pod uwagę licznik i mianownik dodatniej liczby wymiernej w najniższych kategoriach, określa ułamek binarny, który odpowiada pozycji tej frakcji w drzewie Sterna-Brocota.

Przykłady wejść i wyjść podano poniżej:

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)

Dane wejściowe, których nie potrzebujesz obsługiwać, ale zostały uwzględnione w celach informacyjnych:

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

Zwycięża najkrótszy program w dowolnym języku, aby osiągnąć ten cel.

Joe Z.
źródło
Czy są jakieś wymagania dotyczące wejścia / wyjścia? np. Czy wystarczy funkcja jak w rozwiązaniu referencyjnym, czy też musi to być samodzielny program? Czy format wyjściowy ułamkowy ma znaczenie?
Darren Stone,
Funkcja jest wystarczająca. Wyjaśnię to w opisie problemu.
Joe Z.
Trochę za późno na myślenie o tym; Prawdopodobnie postaram się wyjaśnić to jutro.
Joe Z.
2
Ok, myślę, że bijection, o którym myślisz, polega na przypisaniu każdej głębokości drzewa stałego mianownika 2 ^ (głębokość + 1) i liczników 1, 3, 5, 7, ... od lewej.
Peter Taylor,
1
Alternatywnym sposobem konstruowania go to do pierwszej liczby węzłów drzewa wszerz pierwszego uruchomienia zamówienia u 1 (to znaczy 1/1 => 1, 1/2 => 2, 2/1 => 3, 1/3 => 4, itd.). Jeśli liczba wygenerowana w ten sposób dla węzła wynosi n, wówczas 2^lg n(log binarny) jest najwyższym ustawionym bitem n, a pożądana część binarna to (2*(n - 2^lg n) + 1) / 2^(lg n + 1). (Każdy, kto spróbuje asemblera w zestawie instrukcji z bitem najwyższego zestawu, prawdopodobnie będzie chciał skorzystać z tego podejścia).
Peter Taylor

Odpowiedzi:

1

GolfScript ( 49 48 46 znaków)

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

lub

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

Obie są funkcjami, które przyjmują licznik i mianownik na stos i pozostawiają licznik i mianownik na stosie. Demo online .

Podstawowa idea wyrażona jest w pseudokodzie w sekcji 4.5 Matematyka konkretna (p122 w moim wydaniu):

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

Jeśli ciąg Ls i Rs jest interpretowany jako wartość binarna z L = 0 i R = 1, wówczas dwukrotność tej wartości plus jeden jest licznikiem, a mianownik jest o jeden bit dłuższy.

Jako przedmiot zainteresowania Golfscripters, jest to jedna z tych rzadkich sytuacji, w których odkryłem, że jest to przydatne. (Ok, używam go tylko jako licznika pętli, ale to lepsze niż nic).

Peter Taylor
źródło
1

Mathematica, 130 114 111 znaków

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

Przykład:

f[2/3]

3/8

f[4/7]

9/32

f[1]

1/2

alephalpha
źródło
1

Ruby, 132 125

Rubied i grał w golfa referencyjne rozwiązanie z @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

Przykłady użycia:

>> t(2,3)
=> [3, 8]
>> t(4,7)
=> [9, 32]
>> t(1,1)
=> [1, 2]
Darren Stone
źródło
1

Ruby (69 znaków) CoffeeScript (59 znaków)

Jest to funkcja, która przyjmuje argumenty i mianownik jako argumenty i zwraca tablicę zawierającą licznik i mianownik po bijectcji.

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]

Demo online

Wykorzystuje to samo podejście, co moje powyższe rozwiązanie GolfScript, ale jest o wiele bardziej czytelne, ponieważ mogę używać 4 zmiennych bez martwienia się o boksowanie i rozpakowywanie do tablicy. Wybrałem CoffeeScript, ponieważ nie zawiera on prefiksu zmiennych $(20 znaków zapisanych np. W PHP), ma krótką składnię definicji funkcji, która pozwala na domyślne wartości parametrów (więc nie ma potrzeby zawijania f(a,b,x,y)funkcji g(a,b) = f(a,b,0,1)), i pozwala mi używać booleanów jako liczb całkowitych w wyrażenia o użytecznych wartościach. Dla tych, którzy go nie znają, CoffeeScript nie ma standardowego operatora trójskładnikowego w stylu C ( C?P:Q), ale mogę go C&&P||Qtutaj zastąpić , ponieważ Pnigdy nie będzie fałszem.

Prawdopodobnie bardziej elegancką, ale niewątpliwie krótszą alternatywą jest zastąpienie powtarzanego odejmowania dzieleniem i 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 znaków; demo online ). Pisanie w ten sposób ujawnia związek z algorytmem Euclida.

Peter Taylor
źródło
Nie potrzebujesz nawiasów, wokół a<bktórych oszczędzasz jeden znak. Inlining cdaje kolejne dwa. Możesz również rozważyć składnię f=->a,b,x=0,y=1{...}jeszcze krótszej definicji.
Howard,
@Howard, nie wiem, której wersji Ruby używasz, ale w IDEOne pojawia się błąd składniowy, jeśli spróbuję usunąć te nawiasy lub użyć składni tej funkcji.
Peter Taylor,
Spróbuj c=a<b ?z dodatkową spacją po b. W przeciwnym razie znak zapytania jest traktowany jako część b.
Howard,
0

Python - 531

Niekluczone rozwiązanie w Pythonie, które ma służyć jako rozwiązanie referencyjne ostatniego miejsca:

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

Po prostu wykonuje binarne wyszukiwanie między ułamkami, wykorzystując fakt, że mediana dowolnych dwóch ułamków zawsze będzie znajdować się między wartościami tych dwóch ułamków.

Joe Z.
źródło
0

GolfScript, 54 znaki

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

Dane wejściowe należy podać na STDIN w formie określonej w zadaniu. Możesz wypróbować kod online .

> 4/7
9/32

> 9/7
35/64

> 5/1
31/32
Howard
źródło
0

Mathematica 138

Nie tak usprawniona jak procedura alephalpha, ale była to najlepsza, jaką do tej pory mogłem wyprodukować.

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]

Testowanie

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

3/8
9/32
1/2

DavidC
źródło
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}}}

może być mniej, ale lubię czytelny golf

kocioł
źródło
0

Haskell , 125 bajtów

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

Wypróbuj online!

Dane wejściowe i wyjściowe w postaci pary (n,d).

Krótkie wyjaśnienie:

nkonstruuje następny wiersz z poprzedniego, patrząc na każdą parę frakcji i wstawiając nowy między pierwszą a rekurencyjną (która umieści tam drugą frakcję). Przypadek podstawowy jest bardzo prosty, ponieważ jest to po prostu funkcja tożsamości. tFunkcja iteracje tej funkcji w nieskończoność w oparciu od stanu początkowego tylko z dwóch frakcji brzegowych. tnastępnie indeksuje każdy wiersz ( i) i każdy element w wierszu ( j) i szuka pierwszej frakcji pasującej do tego, czego szukamy. Gdy się znajdzie, daje jjako licznik i 2^imianownik.

użytkownik 1472751
źródło