Скорее узловатая головоломка

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 пиксель, однако нижние и перемычки должны быть четко различимы (размер разрыва в веревке должен быть больше толщины веревки как минимум на пиксель с любой стороны) ,

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

Все, что я знаю о своей нотации: я считаю, что есть последовательности значений, которые, кажется, не соответствуют ни одному узлу или узлу. Например, последовательность [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
Вы, вероятно, должны запретить buildins для этого. (В этот момент я был бы шокирован, если бы у Mathematica его не было.)
7
@ ais523 Теперь я не могу использовать KnotDataв Mathematica ...: '(
JungHwan Мин
1
Мне любопытно, какую нотацию вы используете для диаграммы пересечения узлов. У него есть имя? Возможно ли, чтобы два неэквивалентных узла имели одинаковый массив?
xnor
2
@ ais523: Mathematica полностью встроена Knot! Пример использования: << Units`; Convert[Knot, Mile/Hour]доходность 1.1507794480235425 Mile/Hour. (Я думаю, что это смешно, независимо от того, правда это или нет, но на самом деле это правда.)
Грег Мартин
1
[0], [0,1], [0,1,2], [1,0] и множество других массивов создают «узлы», которые эквивалентны узлу, однако вывод простого цикла будет некорректным в любой из этих случаев. Обозначение [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])и в итоге получил следующее:

┌┐┌┐
└─│┘
 └┘ 

Неуправляемая версия с комментариями

Подсветка синтаксиса в Stack Exchange, похоже, имеет неправильный символ комментария для Пролога, поэтому вместо %комментариев (которые фактически использует Пролог) в этом объяснении используются % #комментарии (которые, конечно, эквивалентны из-за того, что начинаются с %, но выделяются правильно).

% # 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])
J. Antonio Perez
Есть ли способы сделать программу более эффективной?
Х. Антонио Перес
Я подозреваю, что программу можно сделать более эффективной, но не существует очевидного или простого способа. Изменение порядка оценки для перемещения по узлу, а не слева направо / сверху вниз, вероятно, поможет, но я не уверен, насколько это поможет (и это также будет существенно больше кода, таким образом не жизнеспособный в коде-гольфе ).
Вы понимаете, почему я не хочу, верно? Я имею в виду ... даже самый лучший код должен быть протестирован, а ваше решение настолько сложное, что я даже не могу убедиться, что оно воспроизведет самый простой узел (узел [2, 0, 1]).
Х. Антонио Перес
Я понимаю, да. Однако это как бы присуще коду-гольфу , особенно когда речь идет о Прологе. Код на самом деле очень прост, поэтому он работает так медленно; code-golf для сложной проблемы почти всегда приводит к решению "грубой силы", которое проверяет все возможные результаты на соответствие спецификации. Если сделать что-то, чтобы сделать программу более эффективной, это сделало бы ее значительно длиннее и, возможно, затруднило бы ее понимание и доказательство правильности.