Зробіть будь-яке число, повторно додаючи 2 числа


14

Вам надається машина з двома 16-бітовими регістрами xта y. Регістри ініціалізуються x=1і y=0. Єдина операція, яку може зробити машина, - це додавання модуля 65536. Це:

  • x+=y- xзамінюється на (x + y) mod 65536; yє незмінним
  • y+=x - аналогічно для y
  • x+=x- xзамінюється на 2x mod 65536; легальний, лише якщо xрівний
  • y+=y - аналогічно для y

Мета - отримати заздалегідь задане число в одному з регістрів (або xабо y).

Написати програму або підпрограму , яка приймає значення (в stdin, argvпараметр функції, верх стека або будь-якого іншого звичайного місця), і виводить програму , щоб отримати цей номер. Вихід повинен перейти stdoutабо (якщо у вашій мові немає stdout) будь-якого іншого звичайного пристрою виведення.

Вихідна програма може бути до 100% плюс 2 кроки, далекі від оптимальних. Тобто, якщо найкоротша програма для отримання цільового номера має nкроки, ваше рішення не може бути довшим за 2n+2. Це обмеження дозволяє уникнути "занадто легких" рішень (наприклад, підрахунок 1, 2, 3, ...), але не потребувати повної оптимізації; Я думаю, що найкоротшу програму знайти найпростіше, але не можу бути впевненою ...

Наприклад: Введення = 25. Вихід:

y+=x
x+=y
x+=y
x+=x
x+=x
x+=x
y+=x

Інший приклад: для будь-якого числа вольових значень вихід має такий змінний малюнок. Для Input = 21, вихід є

y+=x
x+=y
y+=x
x+=y
y+=x
x+=y
y+=x

Найкоротший код (вимірюється в байтах) виграє.

(ця головоломка була натхнена деяким кодом для 16-бітного процесора, який мені довелося генерувати нещодавно)

PS Цікаво - для якого числа оптимальна програма найдовша?


9
З цікавості, чому x+=xзаконний лише якщо xрівний? Також для найкоротшої програми я думаю, що щось на зразок BFS могло б працювати.
Sp3000

Після прибуття до цілі можна продовжити перехід до наступного цільового номера; щоб мати можливість дістатися до будь-якої цілі, одне з номерів повинно бути непарним. Я не хотів створювати нескінченний потік цілей, щоб уникнути зайвої складності, але дух - дозволити такий потік ...
anatolyg

Я змінив обмеження на кількість кроків, тому для цільових номерів 0 або 1 вихідна програма не повинна бути порожньою.
анатоліг

3
якщо x+=xпрацює лише для парних xs, то як узяти приклад для введення 25 парних 3?
bcsb1001

Відповіді:


2

CJam, 31

Як і у відповіді @Tobia , мій алгоритм також безсоромно викрадений натхненним відповіддю @CChak . Але, володіючи чорною магією CJam, мені вдалося зробити ще меншу реалізацію алгоритму.

Спробуйте тут.

Гольф:

qi{_4%!:X)/X!-'xX+"
y+="@}h;]W%`

Безголівки:

qi          "Read input and convert to integer.";
{           "Do...";
  _4%!:X    "Assign (value mod 4 == 0) to X.";
  )/X!-     "If X, divide value by 2. If not X, decrement value.";
  'xX+      "If X, put 'y' on the stack. If not X, put 'x' on the stack.";
  "
y+="        "Put '\ny+=' on the stack.";
  @         "Rotate top 3 elements of stack left so the value is on top.";
}h          "... while value is not zero.";
;           "Discard zero value on stack.";
]W%         "Collect stack into array and reverse.";

Будь ласка, виправте мене, якщо я помиляюся, але я вважав, що операція modulo 65536, що використовується у відповідях з аналогічним алгоритмом, непотрібна. Я інтерпретував питання таким чином, що можна припустити, що вхід буде дійсним 16-бітовим цілим числом без підпису, і будь-які проміжні значення або результати цього алгоритму будуть також.


Цікавий момент щодо видалення мода 65536. Я думаю, ви зробите гарний випадок, що "заздалегідь визначений номер" повинен бути в межах 0-65535.
CChak

8

Perl 107 97

Перший пост, так ось тут.

sub h{($i)=@_;return if(($i%=65536)==0);($i%4==0)?do{h($i/2);say"y+=y";}:do{h($i-1);say"y+=x";}}

Що відповідає всім критеріям додавання реєстру, але я не провів вичерпної перевірки, щоб побачити, чи завжди моя відповідь була в межах 2n + 2 від оптимальної кількості кроків. Хоча це всередині межі для кожного числа Фібоначчі.

Ось більш детальна розбивка

sub h{                           # Declare the subroutine, it should be called referencing an integer value
   ($i)=@_;                      # Assign the i variable to the integer used in the call
   return if(($i%=65536)==0);    # Check for base condition of called by 0, and enforce modulo from the start.
   ($i%4==0) ?                   # If the value passed is even, and will result in an even number if we halve it
   do{h($i/2);say"y+=y";}        # Then do so and recurse 
   :do{h($i-1);say"y+=x";}       # Otherwise "subtract one" and recurse
}                                # Note that the print statements get called in reverse order as we exit.

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

Цікаво, що ми можемо зменшити код на 11 байт * та підвищити нашу «ефективність» щодо кількості операцій реєстру, послабивши вимогу, що можна «подвоїти» лише рівні значення. Тут я включив це для розваги:

sub h{($i)=@_;return if(($i%=65536)==0);($i%2==0)?do{h($i/2);say"y+=y";}:do{h($i-1);say"y+=x";}}

Початок доповнення:

Дуже сподобалась ця проблема, і я останній тиждень спіткав з нею. Думав, що я опублікую свої результати.

Деякі цифри:

Використовуючи алгоритм BFS для пошуку оптимального рішення, у перших 2 ^ 16 числах є лише 18 чисел, на які потрібно 23 кроки. Це: 58558, 59894, 60110, 61182, 61278, 62295, 62430, 62910, 63422, 63462, 63979, 64230, 64314, 4486, 64510, 64698, 64854, 65295.

Використовуючи описаний вище рекурсивний алгоритм, "найскладнішим" номером є 65535 за 45 операцій. (65534 займає 44, а є 14 чисел, які роблять 43 кроки) 65535 - це також найбільший відхід від оптимального, 45 проти 22. Різниця в 23 кроки становить 2n + 1. (Лише три числа потрапляють у 2n: 65534, 32767, 32751.) За винятком тривіальних (нульових кроків) випадків у визначеному діапазоні рекурсивний метод в середньому приблизно в 1,4 рази перевищує оптимальне рішення.

Підсумок: Для чисел 1-2 ^ 16 рекурсивний алгоритм ніколи не переступає визначений поріг 2n + 2, тому відповідь справедлива. Я підозрюю, що це почне занадто далеко від оптимального рішення для більших регістрів / більше біт.

Код, який я використовував для створення BFS, був неохайним, об'єм пам'яті, не коментований і цілеспрямовано не включений. Отже ... не потрібно довіряти моїм результатам, але я в них досить впевнений.


Не-BFS рішення, чудово!
anatolyg

Я думаю, що навіть для найбільш патологічних випадків ви будете знаходитись в коефіцієнті 4, можливо, краще (оскільки я знаю лише нижню межу оптимального рішення). Що все-таки досить добре.
раціоналіс

7

Пітон 3, 202 байти

def S(n):
 q=[(1,0,"")];k=65536
 while q:
  x,y,z=q.pop(0)
  if n in{x,y}:print(z);return
  q+=[((x+y)%k,y,z+"x+=y\n"),(x,(x+y)%k,z+"y+=x\n")]+[(2*x%k,y,z+"x+=x\n")]*(~x&1)+[(x,2*y%k,z+"y+=y\n")]*(~y&1)

(Завдяки @rationalis за кілька байт)

Ось надзвичайно базове рішення. Я б хотів, щоб я міг краще грати в останній лінії, але наразі я не маю ідеї. Подзвоніть з S(25).

Програма просто робить простий BFS без кешування, тому це дуже повільно. Ось S(97)для деяких вибірок вибірки:

y+=x
x+=y
x+=x
x+=x
x+=x
x+=x
y+=x
y+=x
x+=y

5

Dyalog APL, 49 символів / байт *

{0=N←⍵|⍨2*16:⍬⋄0=4|N:⎕←'y+=y'⊣∇N÷2⋄⎕←'y+=x'⊣∇N-1}

Алгоритм безсоромно надихнув відповідь @CChak .

Приклад:

    {0=N←⍵|⍨2*16:⍬⋄0=4|N:⎕←'y+=y'⊣∇N÷2⋄⎕←'y+=x'⊣∇N-1} 25
y+=x
y+=x
y+=y
y+=x
y+=x
y+=y
y+=y
y+=x

Безголівки:

{
    N←(2*16)|⍵                 # assign the argument modulo 65536 to N
    0=N: ⍬                     # if N = 0, return an empty value (that's a Zilde, not a 0)
    0=4|N: ⎕←'y+=y' ⊣ ∇N÷2     # if N mod 4 = 0, recurse with N÷2 and *then* print 'y+=y'
    ⎕←'y+=x' ⊣ ∇N-1            # otherwise, recurse with N-1 and *then* print 'y+=x'
}

* Dyalog APL підтримує застарілу діаграму з символами APL, відображеними на верхні 128 значень байтів. Тому програма APL, яка використовує лише символи ASCII та символи APL, може вважатися байтами == знаками.


3

Пітон, 183

def S(n):
 b,c,e=16,'x+=x\n','x+=y\n';s=d='y+=x\n';a=i=0
 if n<2:return
 while~n&1:n>>=1;a+=1
 while n:n>>=1;s+=[e,c][i]+d*(n&1);i=1;b-=1
 while a:s+=[c,c*b+e*2][i];i=0;a-=1
 print(s)

Я не можу гарантувати, що це залишається в 2 рази оптимальною програмою для парних чисел, але це ефективно. Для всіх дійсних даних 0 <= n < 65536це по суті миттєво і генерує програму щонайменше 33 інструкцій. Для довільного розміру реєстру n(після фіксації цієї константи) знадобиться O(n)час, максимум 2n+1інструкцій.

Деякі бінарні логіки

Будь-яке непарне число nможе бути досягнуто в протягом 31 кроків: робити y+=x, отримувати x,y = 1,1, а потім продовжує подвоюючи xз x+=x(для першого подвоєння зробити x+=y, так як xнепарній , щоб почати с). xтаким чином досягнемо кожної потужності 2 (це лише зсув ліворуч), і тому ви можете встановити будь-який біт yна 1, додавши відповідну потужність 2. Оскільки ми використовуємо 16-бітні регістри, і кожен біт, за винятком для першого потрібно одного подвоєння, щоб досягти, і одного y+=xдля встановлення, ми отримуємо максимум 31 ops.

Будь-яке парне число n- це лише деяка потужність 2, називайте його a, разів непарне число, називайте його m; тобто n = 2^a * m, чи , що еквівалентно, n = m << a. Скористайтеся вищевказаним процесом, щоб отримати m, а потім скиньте xйого, переміщуючи його ліворуч, до досягнення 0. Зробіть a, x+=yщоб встановити x = m, а потім продовжуйте подвоювати x, вперше використовуючи x+=yта згодом використовуючи x+=x.

Незалежно a, воно приймає 16-aзрушення , xщоб отримати y=mі додаткові aзміни для скидання x=0. Наступні aзрушення xвідбудуться після x=m. Тож використовується загальна 16+aзміна. Існує до 16-aбітів, які потрібно встановити m, і кожен з них візьме один y+=x. Нарешті , ми повинні додатковий крок , коли x=0встановити його в м x+=y. Тому потрібно отримати не більше 33 кроків, щоб отримати будь-яке парне число.

Звичайно, ви можете узагальнити це до будь-якого регістру розмірів, і в цьому випадку він завжди має максимум 2n-1і 2n+1ops для непарних і парних nцілих чисел відповідно.

Оптимальність

Цей алгоритм виробляє програму, яка є майже оптимальною (тобто в межах, 2n+2якщо nє мінімальна кількість кроків) для непарних чисел. Для заданого непарного числа n, якщо mй біт є провідним 1, то будь-яка програма займає щонайменше mкроки , щоб дістатися до x=nабо y=n, так як операції , яка збільшує значення в регістрах швидкий є x+=xабо y+=y(тобто подвоєнь) і він приймає mподвоєнь , щоб дістатися до mй біт від 1. Так як цей алгоритм приймає на більшості 2mстадій ( НЕ більше двох за подвоєння, один для зсуву і один y+=x), будь-який непарне число представляється майже оптимальним чином .

Навіть цифри не настільки хороші, оскільки для скидання завжди використовується 16 ops, xперш ніж будь-що інше, а 8, наприклад, можна досягти протягом 5 кроків.

Цікаво, що наведений вище алгоритм взагалі ніколи не використовується y+=y, оскільки yзавжди залишається непарним. У цьому випадку вона може фактично знайти найкоротшу програму для обмеженого набору лише з 3 операцій.

Тестування

# Do an exhaustive breadth-first search to find the shortest program for
# each valid input
def bfs():
    d = {(0,1):0}
    k = 0xFFFF
    s = set(range(k+1))
    current = [(0,1)]
    nexts = []
    def add(pt, dist, n):
        if pt in d: return
        d[pt] = dist
        s.difference_update(pt)
        n.append(pt)
    i = 0
    while len(s) > 0:
        i += 1
        for p in current:
            x,y = p
            add((x,x+y&k), i, nexts)
            add((y,x+y&k), i, nexts)
            if y%2 == 0: add(tuple(sorted((x,y+y&k))), i, nexts)
            if x%2 == 0: add(tuple(sorted((x+x&k,y))), i, nexts)
        current = nexts
        nexts = []
        print(len(d),len(s))

# Mine (@rationalis)
def S(n):
    b,c,e=16,'x+=x\n','x+=y\n';s=d='y+=x\n';a=i=0
    if n<2:return ''
    while~n&1:n>>=1;a+=1
    while n:n>>=1;s+=[e,c][i]+d*(n&1);i=1;b-=1
    while a:s+=[c,c*b+e*2][i];i=0;a-=1
    return s

# @CChak's approach
def U(i):
    if i<1:return ''
    return U(i//2)+'y+=y\n' if i%4==0 else U(i-1)+'y+=x\n'

# Use mine on odd numbers and @CChak's on even numbers
def V(i):
    return S(i) if i % 2 == 1 else U(i)

# Simulate a program in the hypothetical machine language
def T(s):
    x,y = 1,0
    for l in s.split():
        if l == 'x+=x':
            if x % 2 == 1: return 1,0
            x += x
        elif l == 'y+=y':
            if y % 2 == 1: return 1,0
            y += y
        elif l == 'x+=y': x += y
        elif l == 'y+=x': y += x
        x %= 1<<16
        y %= 1<<16
    return x,y

# Test a solution on all values 0 to 65535 inclusive
# Max op limit only for my own solution
def test(f):
    max_ops = 33 if f==S else 1000
    for i in range(1<<16):
        s = f(i); t = T(s)
        if i not in t or len(s)//5 > max_ops:
            print(s,i,t)
            break

# Compare two solutions
def test2(f,g):
    lf = [len(f(i)) for i in range(2,1<<16)]
    lg = [len(g(i)) for i in range(2,1<<16)]
    l = [lf[i]/lg[i] for i in range(len(lf))]
    print(sum(l)/len(l))
    print(sum(lf)/sum(lg))

# Test by default if script is executed
def main():
    test()

if __name__ == '__main__':
    main()

Я написав простий тест, щоб перевірити, чи справді моє рішення дає правильні результати, і ніколи не проходить понад 33 кроки для всіх дійсних входів ( 0 <= n < 65536).

Крім того, я спробував зробити емпіричний аналіз для порівняння результатів мого рішення з оптимальними результатами - однак, виявляється, що пошук в першу чергу є надто неефективним, щоб отримати мінімальну довжину виходу для кожного дійсного вводу n. Наприклад, використання BFS для пошуку результату n = 65535не закінчується за розумну кількість часу. Тим не менш, я пішов bfs()і я відкритий для пропозицій.

Я проте протестував власне рішення проти @ CChak's (реалізований у Python тут як U). Я очікував, що міна зробить гірше, оскільки вона є різко неефективною для менших парних чисел, але в середньому по всьому діапазону двома способами, шахта дала вихід довжини в середньому на 10,8% до 12,3% коротше. Я думав, можливо, це пов’язано з кращою ефективністю мого власного рішення на непарних числах, такV використовує міну на непарні числа і @ CChak на парні числа, але Vзнаходиться між ними (приблизно на 10% коротше U, на 3% довше, ніж S).


1
Досить багато логіки в 201 байті!
anatolyg

@analtolyg Що я можу сказати, мені подобається математика і біт. Я можу дослідити інші підходи, оскільки рішення з парним числом має можливість вдосконалитись.
раціоналіс

Ой, я навіть не розумів, що x,y='xy'це можливо досі. На жаль, я не можу придумати спосіб c*b+e*2короткого переписування з %форматуванням.
раціоналіс

Ах, я не здогадувався, що ти використовував його десь ще Це тільки я, або S(2)вихід у нас справді довгий?
Sp3000

На жаль, з мого рішення кожне парне число робить щонайменше 19 кроків ( S(2)найкоротший - 19). Я не відслідковую це xі yявно, тому, хоча xдосягає 2 після другого кроку, він все-таки продовжує скидатись xдо 0. Я відчуваю, ніби має бути краще рішення, але поки що я не можу думати про це один.
раціоналіс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.