Доволі вузька головоломка


23

Напишіть програму, щоб намалювати двовимірну діаграму вузла на основі структури вузла. Вузол - це саме те, що звучить: петля мотузки, яка зав'язана. У математиці діаграма вузлів показує, де шматок мотузки перетинається над або під собою, щоб утворити вузол. Деякі приклади діаграм вузлів наведені нижче:

введіть тут опис зображення

У лінії, де мотузка перетинає себе, перервається.

Вхід: вхід, що описує вузол, є масивом цілих чисел. Вузол, де мотузка перетинає себе n разів, може бути представлена ​​як масив з n цілих чисел, кожне зі значенням у діапазоні [0, n-1]. Назвемо цей масив K .

Щоб отримати масив, що описує вузол, пронумеруйте кожен із сегментів 0 до n-1. Сегмент 0 повинен вести до сегмента 1, який повинен вести до сегмента 2, який повинен вести до сегмента 3, і так далі, поки сегмент n-1 не буде петлею назад і не призведе до сегмента 0. Сегмент закінчується, коли через нього перетинається інший сегмент мотузки ( представлена ​​перервою у рядку на діаграмі). Візьмемо найпростіший вузол - вузол трилистника. Після того, як ми пронумерували сегменти, сегмент 0 закінчується, коли сегмент 2 перетинає його; сегмент 1 закінчується, коли сегмент 0 перетинає його; а сегмент 2 закінчується, коли сегмент 1 перетинає його. Таким чином, масив, що описує вузол, є [2, 0, 1]. Загалом, сегмент x починається там, де відрізаний відрізок x-1 mod n , і закінчується там, де відрізок K [x] перетинає його.

На зображенні нижче представлені діаграми вузлів із позначеними сегментами та відповідними масивами, що описують вузол.

введіть тут опис зображення

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

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

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

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

Все, що я знаю про моє позначення: я вважаю, що є послідовності значень, які, здається, не відповідають жодному вузлу чи unknot. Наприклад, послідовність [2, 3, 4, 0, 1], здається, неможливо намалювати.

Припустимо, що ви переходите на перехрестя і, починаючи з цього перетину, слідуйте по шляху мотузки в одну сторону і позначте кожен маркований перетин, на який ви зіткнулися, з послідовно більшими інтегральними значеннями. Для чергування вузлів існує простий алгоритм перетворення моєї позначення в таку маркування, а для чергування вузлів тривіально конвертувати цю маркування в код Гаусса:

template<size_t n> array<int, 2*n> LabelAlternatingKnot(array<int, n> end_at)
{
    array<int, n> end_of;
    for(int i=0;i<n;++i) end_of[end_at[i]] = i;
    array<int, 2*n> p;
    for(int& i : p) i = -1;
    int unique = 0;
    for(int i=0;i<n;i++)
    {
        if(p[2*i] < 0)
        {
            p[2*i] = unique;
            p[2*end_of[i] + 1] = unique;
            ++unique; 
        }
        if(p[2*i+1] < 0)
        {
            p[2*i+1] = unique;
            p[2*end_at[i]] = unique;
            ++unique;
        }
    }
    return p;
}
template<size_t n> auto GetGaussCode(array<int, n> end_at)
{
    auto crossings = LabelAlternatingKnot(end_at);
    for(int& i : crossings) ++i;
    for(int i=1;i<2*n;i+=2) crossings[i] = -crossings[i];
    return crossings;
}

4
Ви, мабуть, повинні заборонити вбудованим для цього робити. (На даний момент я був би шокований, якщо у Mathematica цього немає .)

7
@ ais523 Тепер я не можу використовувати KnotDataв Mathematica ...: '(
JungHwan Min

1
Мені цікаво позначення, яке ви використовуєте для діаграми перетину вузлів. Чи має це ім’я? Чи можливо два нееквівалентні вузли мати один і той же масив?
xnor

2
@ ais523: Mathematica повністю має Knotвбудований! Приклад використання: << Units`; Convert[Knot, Mile/Hour]врожайність 1.1507794480235425 Mile/Hour. (Я думаю, що це смішно незалежно від того, правда це чи неправда; але насправді це правда.)
Грег Мартін

1
[0], [0,1], [0,1,2], [1,0] та безліч інших масивів створюють "вузли", еквівалентні unknot, однак виведення простої петлі було б неправильним у будь-який із цих випадків. Позначення [0] дуже конкретно означає петлю мотузки, яка перетинає себе рівно один раз, і дуже легко сказати, чи відповідає малюнок на екрані вхідним позначенням чи ні.
Дж. Антоніо Перес

Відповіді:


22

GNU Prolog, 622 634 668 байт у кодовій сторінці 850

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

Оновлення : Мабуть, правила PPCG вимагають додаткового коду, щоб зробити програму вихід і відновити стан саме таким, яким він був на початку. Це робить програму дещо довшою і не додає їй ніякого алгоритмічного інтересу, але в інтересах дотримання правил я змінив її.

Програма для гольфу

Використання GNU Prolog, оскільки він має синтаксис вирішення обмежень, який трохи коротший, ніж арифметичний синтаксис портативного Prolog, економлячи кілька байт.

y(A,G):-A=1;A= -1;A=G;A is-G.
z(A/B,B,G):-y(A,G),y(B,G),A=\= -B.
v(D,E,G):-E=1,member(_-_,D),p(D);F#=E-1,nth(F,D,M),(M=[_];M=L-S/R,z(L,O,G),J is F+O,nth(J,D,I/U-T/Q),(I=O,Q#=R-1,S=T;K is J+O,R=0,n(S-T-V),y(U,G),U\=O,U=\= -O,I=U,nth(K,D,O/_-V/_))),v(D,F,G).
i([H|K],S):-K=[]->assertz(n(S-H-0));T#=S+1,assertz(n(S-H-T)),i(K,T).
t([],1,_):-!.
t(D,R,G):-S#=R-1,t(E,S,G),H#=G-1,length(F,H),append(F,[[x]|E],D).
s(1,2).
s(-1,1).
s(S,T):-S>1->T=3;T=0.
r(I/O-_,C):-s(I,J),s(O,P),N#=J*4+P+1,nth(N,"│┐┌?└─?┌┘?─┐?┘└│",C).
r([X],C):-X\=y->C=10;C=32.
p([]).
p([H|T]):-r(H,C),put_code(C),!,p(T).
g(4).
g(G):-g(H),G#=H+1.
m(K):-i(K,0),g(G),t(D,G,G),length(D,L),v(D,L,G),abolish(n/1).

Алгоритм

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

Вихід здійснюється через термінальний арт. GNU Prolog, здається, хоче отримати один байтовий набір символів, який відповідає ASCII, але неважливо, який з них використовується (оскільки він розглядає символи з високим бітовим набором як непрозорі). Як результат, я використав IBM850, який широко підтримується та має потрібні нам символи для малювання ліній.

Вихід

Програма здійснює пошук усіх можливих зображень вузлів у порядку розміру їх обмежувального поля, потім виходить, коли знайде перше. Ось як виглядає вихід m([0]).:

 ┌┐
┌│┘
└┘ 

На моєму комп’ютері знадобилося 290.528 секунд; програма не дуже ефективна. Я залишив його працювати протягом двох годин m([0,1])і закінчив це:

┌┐┌┐
└─│┘
 └┘ 

Безгольова версія з коментарями

У синтаксисі маркера синтаксису стека, здається, є неправильний символ коментаря для Prolog, тому замість %коментарів (які Prolog насправді використовує) це пояснення використовує % #коментарі (які, звичайно, еквівалентні, починаючи з початку %, але правильно виділяються).

% # Representation of the drawing is: a list of:
% #     indelta/outdelta-segment/distance  (on path)
% # and [x] or [_]                         (off path; [x] for border).
% # A drawing is valid, and describes a knot, if the following apply
% # (where: d[X] is the Xth element of the drawing,
% #         k[S] is the Sth element of the input,
% #         n[S] is S + 1 modulo the number of sections):
% # d[X]=_/O-S-R, R>1 implies d[X+O]=O/_-S-(R-1)
% # d[X]=_/O-S-0 implies d[X+O]=_/_-k[S]-_
% #                  and d[X+O*2]=O/_-n[S]-_
% # all outdeltas are valid deltas (±1 row/column)

% # not technically necessary, but makes it possible to compile the
% # code (and thus makes the program faster to test):
:- dynamic([n/1]).

% # legal delta combinations:
y(A,G):-A=1;A= -1;              % # legal deltas are 1, -1,
        A=G;A is-G.             % # grid size, minus grid size
z(A/B,B,G):-y(A,G),y(B,G),      % # delta components are valid
            A=\= -B.            % # doesn't U-turn
% # z returns the outdelta for convenience (= byte savings) later on

% # We use v (verify) to verify the first E-1 elements of a drawing D.
% # nth is 1-indexed, so we recurse from length(D)+1 down to 2, with
% # 1 being the trivial base case. After verifying, the grid is printed.
% # This version of the program causes v to exit with success after
% # printing one grid (and uses p to do the work of deciding when that is).
v(D,E,G):-E=1,                  % # base case:
          member(_-_,D),        % # ensure the grid is nonempty
          p(D);                 % # print the grid (and exit)

                                % # otherwise, recursive case:
          F#=E-1,nth(F,D,M),    % # check the last unchecked element
          (
            M=[_];              % # either it's not on the path; or
            M=L-S/R,            % # it's structured correctly, and
            z(L,O,G),           % # it has a valid delta;
            J is F+O,           % # find the outdelta'd element index
            nth(J,D,I/U-T/Q),   % # and the outdelta'd element
            (
              I=O,Q#=R-1,S=T;   % # if not an endpoint, points to next pixel
              K is J+O,         % # else find segment beyond the path:
              R=0,              % # it's an endpoint,
              n(S-T-V),         % # it points to the correct segment,
              y(U,G),           % # ensure we can do NOT comparisons on U
              U\=O,U=\= -O,     % # the line we jump is at right angles
              I=U,              % # the line we jump is straight
              nth(K,D,O/_-V/_)  % # the pixel beyond has a correct indelta,
                                % # and it has the correct segment number
            )
          ),
          v(D,F,G).             % # recurse

% # We use i (init) to set up the k, n tables (k and n are fixed for
% # any run of the program, at least). S is the number of elements that
% # have been removed from K so far (initially 0). To save on characters,
% # we combine k and n into a single predicate n.
i([H|K],S):-K=[]->             % # if this is the last element,
            assertz(n(S-H-0)); % # section 0 comes after S;
            T#=S+1,            % # otherwise, add 1 to S,
            assertz(n(S-H-T)), % # that section comes after S,
            i(K,T).            % # and recurse.

% # We use t (template) to create a template drawing. First argument is
% # the drawing, second argument is the number of rows it has plus 1,
% # third argument is the number of columns it has plus 1.
t([],1,_):-!.
t(D,R,G):-S#=R-1,t(E,S,G),      % # recurse,
          H#=G-1,length(F,H),   % # F is most of this row of the grid
          append(F,[[x]|E],D).  % # form the grid with F + border + E

% # We use s (shrink) to map a coordinate into a value in the range 0, 1, 2, 3.
s(1,2).
s(-1,1).
s(S,T):-S>1->T=3;T=0.
% # We use r (representation) to map a grid cell to a character.
r(I/O-_,C):-s(I,J),s(O,P),N#=J*4+P+1,nth(N,"│┐┌?└─?┌┘?─┐?┘└│",C).
r([X],C):-X\=y->C=10;C=32.
% # We use p (print) to pretty-print a grid.
% # The base case allows us to exit after printing one knot.
p([]).
p([H|T]):-r(H,C),put_code(C),!,p(T).

% # We use g (gridsize) to generate grid sizes.
g(4).
g(G):-g(H),G#=H+1.

% # Main program.
m(K):-i(K,0),                  % # initialize n
      g(G),                    % # try all grid sizes
      t(D,G,G),                % # generate a square knot template, size G
      length(D,L),             % # find its length
      v(D,L,G),                % # verify and print one knot
      % # Technically, this doesn't verify the last element of L, but we know
      % # it's a border/newline, and thus can't be incorrect.
      abolish(n/1).            % # reset n for next run; required by PPCG rules

Я завантажив пролог GNU, скопіював ваш код у .txt-файл, зберег його як .pl-файл, закодований ascii, а в консолі під назвою m ([0])
Дж. Антоніо Перес

Чи є способи зробити програму більш ефективною?
Дж. Антоніо Перес

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

Ви розумієте, чому я неохоче, правда? Я маю на увазі ... навіть найкращий код потрібно перевірити, і ваше рішення настільки складне, що я навіть не можу перевірити, що він буде відтворювати найпростіший вузол (вузол [2, 0, 1]).
Дж. Антоніо Перес

Я розумію, так. Однак це своєрідно притаманне коду-гольфу , особливо якщо мова йде про Prolog. Код насправді дуже простий, через що він працює так повільно; Код-гольф на складній проблемі в значній мірі завжди призводить до жорстокого рішення, яке перевіряє всі можливі результати відповідно до специфікації. Якщо щось зробити, щоб програма стала більш ефективною, це зробило б її значно довше, а також, можливо, ускладнить розуміння та підтвердження правильності.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.