Um enigma bastante nodoso


23

Escreva um programa para desenhar um diagrama 2-D de um nó com base na estrutura do nó. Um nó é exatamente o que parece: um laço de corda amarrado. Na matemática, um diagrama de nós mostra onde um pedaço de corda cruza-se sobre ou embaixo de si para formar o nó. Alguns exemplos de diagramas de nós são mostrados abaixo:

insira a descrição da imagem aqui

Há uma quebra na linha onde a corda cruza sobre si mesma.

Entrada: a entrada que descreve o nó é uma matriz de números inteiros. Um nó em que a corda se cruza n vezes pode ser representado como uma matriz de n números inteiros, cada um com um valor no intervalo [0, n-1]. Vamos chamar essa matriz K .

Para obter a matriz que descreve um nó, numere cada um dos segmentos de 0 a n-1. O segmento 0 deve levar ao segmento 1, que deve levar ao segmento 2, que deve levar ao segmento 3, e assim sucessivamente até o segmento n-1 voltar e retornar ao segmento 0. Um segmento termina quando outro segmento de corda passa por ele ( representado por uma quebra na linha do diagrama). Vamos dar o nó mais simples - o nó do trevo. Depois de numerarmos os segmentos, o segmento 0 termina quando o segmento 2 passa sobre ele; o segmento 1 termina quando o segmento 0 passa sobre ele; e o segmento 2 termina quando o segmento 1 cruza sobre ele. Assim, a matriz que descreve o nó é [2, 0, 1]. Em geral, o segmento x começa onde o segmento x-1 mod n parou e termina onde o segmento K [x] cruza sobre ele.

A imagem abaixo mostra diagramas de nós, com segmentos rotulados e as matrizes correspondentes que descrevem o nó.

insira a descrição da imagem aqui

Os três diagramas superiores são verdadeiros nós, enquanto os três inferiores são laços de corda que se cruzam sobre si mesmos, mas não são realmente atados (mas que ainda possuem códigos correspondentes).

Sua tarefa é escrever uma função que utilize uma matriz de números inteiros K (você poderia chamar de algo diferente) que descreva um nó (ou laço de corda que não seja realmente atado) e que produz o diagrama de nó correspondente, conforme descrito na descrição acima. exemplos. Se você puder, forneça uma versão não-armazenada do seu código ou uma explicação e também forneça saídas de amostra do seu código. O mesmo nó geralmente pode ser representado de várias maneiras diferentes, mas se o diagrama do nó que a sua função obtiver satisfizer tiver a entrada como uma de suas representações possíveis, sua solução será válida.

Isso é código-golfe, então o código mais curto em bytes vence. A linha que representa a corda pode ter uma espessura de 1 pixel, no entanto, as passagens por baixo e por cima devem ser claramente distinguíveis (o tamanho da quebra na corda deve ser maior que a espessura da corda por pelo menos um pixel de cada lado) .

Promoverei respostas que dependem dos recursos internos da teoria dos nós, mas a que foi selecionada no final não pode contar com os recursos internos da teoria dos nós.

Tudo o que sei sobre minha notação: acredito que existem sequências de valores que parecem não corresponder a nenhum nó ou nó. Por exemplo, a sequência [2, 3, 4, 0, 1] parece impossível de desenhar.

Além disso, suponha que você faça uma travessia e, a partir dessa travessia, siga o caminho da corda em uma direção e rotule cada travessia não identificada que você encontrar com valores integrais sucessivamente maiores. Para nós alternados, existe um algoritmo simples para converter minha notação em uma rotulagem e, para nós alternados, é trivial converter essa rotulação em um código de Gauss:

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
Você provavelmente deve banir os builtins por fazer isso. (Neste ponto, eu ficaria chocado se Mathematica não ter um.)

7
@ ais523 Agora não posso usar KnotDatano Mathematica ...: '(
JungHwan Min 8/16

1
Estou curioso sobre a notação usada no diagrama de cruzamentos de nós. Isso tem um nome? É possível que dois nós não equivalentes tenham a mesma matriz?
Xnor

2
@ ais523: O Mathematica possui totalmente um Knotbuiltin! Exemplo de uso: << Units`; Convert[Knot, Mile/Hour]rendimentos 1.1507794480235425 Mile/Hour. (Eu acho que isso é engraçado, independentemente de saber se é verdadeiro ou falso; mas é realmente verdade.)
Greg Martin

1
[0], [0,1], [0,1,2], [1,0] e uma variedade de outras matrizes produzem "nós" equivalentes ao nó, no entanto a saída de um loop simples seria incorreta. qualquer um desses casos. A notação [0] significa muito especificamente um laço de corda que se cruza exatamente uma vez, e é muito fácil saber se uma figura desenhada na tela satisfaz ou não a notação de entrada.
J. Antonio Perez

Respostas:


22

GNU Prolog, 622 634 668 bytes na página de códigos 850

Atualização : A versão anterior do programa às vezes tornava os cruzamentos tão rígidos que não renderizavam corretamente, o que viola as especificações. Eu adicionei um código extra para evitar isso.

Atualização : Aparentemente, as regras do PPCG exigem código extra para fazer o programa sair e restaurar o estado exatamente como estava no início. Isso torna o programa um pouco mais longo e não agrega nenhum interesse algorítmico a ele, mas, no interesse da conformidade com as regras, eu mudei.

Programa de golfe

Usando o GNU Prolog porque possui uma sintaxe do solucionador de restrições um pouco menor que a sintaxe aritmética portátil do Prolog, economizando alguns bytes.

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).

Algoritmo

Esse é um desses problemas em que é difícil saber como começar. Não é óbvio como calcular a forma do nó a partir da notação fornecida, porque não informa se você pretende dobrar a linha para a esquerda ou para a direita em qualquer local (e, como tal, o notação pode ser ambígua). Minha solução foi, efetivamente, usar o antigo modo de espera para golfe: escreva um programa incrivelmente ineficiente que gere todas as saídas possíveis e depois veja se elas correspondem à entrada. (O algoritmo usado aqui é um pouco mais eficiente, pois o Prolog pode eliminar o beco sem saída ocasional, mas não possui informações suficientes para melhorar a complexidade computacional.)

A saída é via terminal art. O GNU Prolog parece querer um conjunto de caracteres de byte consistente com o ASCII, mas não se importa com qual deles é usado (pois trata os caracteres com o bit alto definido como opaco). Como resultado, usei o IBM850, que é amplamente suportado e possui os caracteres de desenho de linha que precisamos.

Saída

O programa pesquisa todas as imagens de nó possíveis, na ordem do tamanho da caixa delimitadora, e sai quando encontra a primeira. Aqui está a aparência da saída m([0]).:

 ┌┐
┌│┘
└┘ 

Demorou 290.528 segundos para executar no meu computador; o programa não é muito eficiente. Deixei em funcionamento por duas horas m([0,1])e acabei com isso:

┌┐┌┐
└─│┘
 └┘ 

Versão ungolfed com comentários

O marcador de sintaxe do Stack Exchange parece ter o símbolo de comentário errado para o Prolog; portanto, em vez de %comentários (que o Prolog realmente usa), esta explicação usa % #comentários (que são obviamente equivalentes devido ao início %, mas destacam corretamente).

% # 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

Eu baixei o prólogo do GNU, copiei o seu código em um arquivo .txt, salvei-o como um arquivo .pl codificado em ASCII e no console chamado m ([0])
J. Antonio Perez

Existem maneiras de tornar o programa mais eficiente?
J. Antonio Perez

Suspeito que o programa possa se tornar mais eficiente, mas não há uma maneira óbvia ou fácil. Alterar a ordem de avaliação para se mover ao longo do nó, em vez de da esquerda para a direita / de cima para baixo, provavelmente ajudaria, mas não tenho certeza de quanto isso ajudaria (e também seria muito mais código, portanto, inviável em um código-golfe ).

Você entende por que estou relutante, certo? Quero dizer ... até o melhor código precisa ser testado, e sua solução é tão complexa que nem posso verificar se ele reproduz o nó mais simples (o nó [2, 0, 1]).
J. Antonio Perez

Eu entendo sim No entanto, isso é inerente ao código-golfe , especialmente quando se trata de Prolog. O código é realmente muito simples, e é por isso que é executado lentamente;code-golf em um problema complexo praticamente sempre leva a uma solução de força bruta que verifica todas as saídas possíveis em relação à especificação. Fazer algo para tornar o programa mais eficiente o tornaria substancialmente mais longo e dificultaria a compreensão e a comprovação da correção.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.