Найти позицию дроби в дереве Штерна-Броко


11

Дерево Штерна-Броко является бинарным деревом фракций , где каждая фракция приобретается путем добавления числителе и знаменателя двух фракций соседних его в указанных выше уровнях.

Он генерируется, начиная с 0/1и 1/0как «фракции конечной точки», и оттуда, итерируя, помещая одну дробь между каждой последовательной парой дробей, добавляя вместе числители и знаменатели этих дробей, например, так:

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

В каждой итерации дерева Штерна-Броко ( nитерация) 2^n + 1в последовательности присутствуют элементы, которым мы можем приписать дробь от 0/2^nдо 2^n/2^n. Каждая новая итерация просто вставляет одну дробь «наполовину» между каждой парой последовательных дробей.

Это делает дерево Штерна-Броко взаимно однозначным отображением между положительными рациональными числами и двоичными дробями между 0 и 1, что также служит доказательством того, что два набора имеют одинаковую мощность.

Ваша задача - написать программу или функцию, которая, учитывая числитель и знаменатель положительного рационального числа в самых низких терминах, определяет двоичную дробь, которая соответствует позиции этой дроби в дереве Штерна-Броко.

Примеры входов и выходов приведены ниже:

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)

Входы, которые вам не нужно поддерживать, но включены для справки:

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

Самая короткая программа на любом языке для достижения этой цели побеждает.


Есть ли какие-либо требования к вводу / выводу? Например, достаточно ли функции, как в вашем эталонном решении, или это должна быть отдельная программа? Имеет ли значение выходной формат дроби?
Даррен Стоун

Функция достаточна. Я поясню это в описании проблемы.
Джо З.

Мне немного поздно думать об этом; Я, наверное, попробую уточнить это завтра.
Джо З.

2
Хорошо, я думаю, что биекция, которую вы имеете в виду, состоит в том, чтобы присвоить каждой глубине дерева постоянный знаменатель 2 ^ (глубина + 1) и числители 1, 3, 5, 7, ... слева.
Питер Тейлор

1
Альтернативный способ его построения является первым номером узлы дерева в ширину-первых стартового порядка на 1 (т.е. 1/1 => 1, 1/2 => 2, 2/1 => 3, 1/3 => 4и т.д.). Если число, сгенерированное таким образом для узла, равно n, то 2^lg n(двоичный журнал) - это самый высокий установленный бит n, и желаемая двоичная дробь равна (2*(n - 2^lg n) + 1) / 2^(lg n + 1). (Любой, кто пытается использовать ассемблерное решение в наборе команд с битом-наивысшего набора, вероятно, захочет использовать этот подход).
Питер Тейлор

Ответы:


1

GolfScript ( 49 48 46 символов)

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

или же

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

Оба являются функциями, которые принимают числитель и знаменатель в стеке и оставляют числитель и знаменатель в стеке. Демо онлайн .

Основная идея выражена в псевдокоде в разделе 4.5 « Бетонная математика » (p122 в моем издании):

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

Если строка Ls и Rs интерпретируется как двоичное значение с L = 0 и R = 1, то в два раза это значение плюс один является числителем, а знаменатель на один бит длиннее.

Как точка интереса для Golfscripters, это один из тех редких случаев, когда я нашел разворачивание полезным. (Хорошо, я использую его только как счетчик циклов, но это лучше, чем ничего).


1

Mathematica, 130 114 111 символов

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

Пример:

f[2/3]

3/8

f[4/7]

9/32

f[1]

1/2


1

Рубин, 132 125

Рубин и игра в гольф эталонное решение от @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

Примеры использования:

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

1

Ruby (69 символов) CoffeeScript (59 символов)

Это функция, которая принимает числитель и знаменатель в качестве аргументов и возвращает массив, содержащий числитель и знаменатель после биекции.

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]

Онлайн демо

В нем используется тот же подход, что и в моем решении GolfScript, описанном выше, но он гораздо более читабелен, потому что я могу использовать 4 переменные, не беспокоясь о том, чтобы упаковать и распаковать в массив. Я выбрал CoffeeScript, потому что он не содержит префиксов переменных $(20 символов сохранены, например, в PHP), имеет короткий синтаксис определения функции, который допускает значения параметров по умолчанию (поэтому нет необходимости переносить f(a,b,x,y)в функцию g(a,b) = f(a,b,0,1)) и позволяет использовать логические значения в качестве целых чисел в выражения с полезными значениями. Для тех, кто этого не знает, у CoffeeScript нет стандартного тернарного оператора в стиле C ( C?P:Q), но я могу заменить его C&&P||Qздесь, потому что Pон никогда не будет ложным.

Возможно более элегантный, но бесспорно менее короткая, альтернатива заменить повторное вычитание с делением и по модулю:

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 символов; онлайн демо ). Написание этого таким образом выявляет связь с алгоритмом Евклида.


Вам не нужны круглые скобки, вокруг a<bкоторых вы экономите один символ. Встраивание cдает еще два. Вы можете также рассмотреть синтаксис f=->a,b,x=0,y=1{...}для еще более короткого определения.
Говард

@Howard, я не знаю, какую версию Ruby вы используете, но в IDEOne я получаю синтаксическую ошибку, если пытаюсь удалить эти скобки или использовать синтаксис этой функции.
Питер Тейлор

Попробуйте c=a<b ?с дополнительным пробелом после b. В противном случае вопросительный знак рассматривается как часть b.
Говард

0

Питон - 531

Разгаданное в Python решение, которое будет служить последним справочным решением:

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

Он просто выполняет двоичный поиск между дробями, используя преимущество того факта, что посредник любых двух дробей всегда будет между значениями этих двух дробей.


0

GolfScript, 54 символа

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

Входные данные должны быть указаны в STDIN в форме, указанной в задании. Вы можете попробовать код онлайн .

> 4/7
9/32

> 9/7
35/64

> 5/1
31/32

0

Mathematica 138

Не такой обтекаемый, как процедура с алефальцами, но это было лучшее, что я смог сделать до сих пор.

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]

тестирование

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

может быть меньше, но мне нравится читаемый гольф


0

Haskell , 125 байт

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

Попробуйте онлайн!

Ввод и вывод в виде пары (n,d).

Краткое объяснение:

nсоздаёт следующую строку из предыдущей, просматривая каждую пару дробей и вставляя новую между первой и рекурсивной (что и поместит вторую дробь прямо там). Базовый случай очень прост, поскольку в основном это просто функция тождества. tФункция перебирает эту функцию на неопределенное время, основанную от начального состояния с помощью всего двух граничных фракций. tзатем индексирует каждую строку ( i) и каждый элемент в строке ( j) и ищет первую дробь, которая соответствует тому, что мы ищем. Когда это находит, это дает jкак числитель и 2^iкак знаменатель.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.