Un enigma piuttosto intricato


23

Scrivi un programma per disegnare un diagramma 2D di un nodo basato sulla struttura del nodo. Un nodo è esattamente quello che sembra: un anello di corda che è legato. In matematica, un diagramma di nodo mostra dove un pezzo di corda attraversa sopra o sotto se stesso per formare il nodo. Di seguito sono riportati alcuni schemi di nodi di esempio:

inserisci qui la descrizione dell'immagine

C'è una rottura nella linea in cui la corda attraversa se stessa.

Input: l'input che descrive il nodo è un array di numeri interi. Un nodo in cui la corda incrocia su se stessa n volte può essere rappresentato come una matrice di n numeri interi, ciascuno con un valore compreso nell'intervallo [0, n-1]. Chiamiamo questo array K .

Per ottenere l'array che descrive un nodo, numerare ciascuno dei segmenti da 0 a n-1. Il segmento 0 dovrebbe condurre al segmento 1, che dovrebbe portare al segmento 2, che dovrebbe portare al segmento 3, e così via fino a quando il segmento n-1 si sposta indietro e conduce al segmento 0. Un segmento termina quando un altro segmento di fune lo attraversa ( rappresentato da un'interruzione nella linea nel diagramma). Facciamo il nodo più semplice: il nodo del trifoglio. Dopo aver numerato i segmnenti, il segmento 0 termina quando il segmento 2 lo attraversa; il segmento 1 termina quando il segmento 0 lo attraversa; e il segmento 2 termina quando il segmento 1 lo attraversa. Pertanto, l'array che descrive il nodo è [2, 0, 1]. In generale, il segmento x inizia da dove il segmento x-1 mod n è stato interrotto e termina dove il segmento K [x] lo attraversa.

L'immagine seguente mostra i diagrammi dei nodi, con segmenti etichettati e le matrici corrispondenti che descrivono il nodo.

inserisci qui la descrizione dell'immagine

I tre diagrammi superiori sono veri nodi, mentre i tre inferiori sono anelli di corda che si incrociano su se stessi ma che in realtà non sono annodati (ma che hanno ancora codici corrispondenti).

Il tuo compito è scrivere una funzione che accetta una matrice di numeri interi K (potresti chiamarla in modo diverso) che descriva un nodo (o un anello di corda che non è effettivamente annodato) e che produce il diagramma del nodo corrispondente, come descritto sopra esempi. Se è possibile, fornire una versione non modificata del codice o una spiegazione e fornire anche output di esempio del codice. Lo stesso nodo può spesso essere rappresentato in più modi diversi, ma se il diagramma dei nodi che la tua funzione soddisfa ha l'input come una delle sue possibili rappresentazioni, la tua soluzione è valida.

Questo è code-golf, quindi vince il codice più breve in byte. La linea che rappresenta la corda può avere uno spessore di 1 pixel, tuttavia gli incroci sotto e gli incroci devono essere chiaramente distinguibili (la dimensione della rottura della corda dovrebbe essere maggiore dello spessore della corda di almeno un pixel su entrambi i lati) .

Valuterò le risposte che si basano sulle capacità integrate della teoria dei nodi, ma quella selezionata alla fine non può fare affidamento sulle capacità integrate della teoria dei nodi.

Tutto quello che so della mia notazione: credo che ci siano sequenze di valori che non sembrano corrispondere a nessun nodo o sregolato. Ad esempio, la sequenza [2, 3, 4, 0, 1] sembra impossibile da disegnare.

A parte ciò, supponi di prendere un incrocio e, partendo da quell'incrocio, segui il percorso della corda in una direzione ed etichetta ogni incrocio senza etichetta che incontri con valori integrali sempre più grandi. Per i nodi alternati, esiste un semplice algoritmo per convertire la mia notazione in tale etichettatura e per i nodi alternati è banale convertire questa etichettatura in un codice di 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
Probabilmente dovresti vietare i builtin per farlo. (A questo punto, sarei scioccato se Mathematica non ne avesse uno.)

7
@ ais523 Ora non posso usare KnotDatain Mathematica ...: '(
JungHwan Min

1
Sono curioso della notazione che usi per il diagramma di attraversamento dei nodi. ha un nome? È possibile che due nodi non equivalenti abbiano lo stesso array?
xnor

2
@ ais523: Mathematica ha totalmente integrato Knot! Esempio di utilizzo: << Units`; Convert[Knot, Mile/Hour]rese 1.1507794480235425 Mile/Hour. (Penso che questo sia divertente indipendentemente dal fatto che sia vero o falso; ma in realtà è vero.)
Greg Martin

1
[0], [0,1], [0,1,2], [1,0] e una varietà di altre matrici producono tutti "nodi" equivalenti al non-nodo, tuttavia l'output di un semplice ciclo sarebbe errato in uno di questi casi. La notazione [0] in modo molto specifico indica un anello di corda che si interseca esattamente una volta, ed è molto facile dire se una figura disegnata sullo schermo soddisfa o meno la notazione di input.
J. Antonio Perez,

Risposte:


22

GNU Prolog, 622 634 668 byte nella tabella codici 850

Aggiornamento : la versione precedente del programma a volte faceva degli incroci così stretti da non renderli correttamente, il che viola le specifiche. Ho aggiunto un codice aggiuntivo per evitarlo.

Aggiornamento : apparentemente le regole PPCG richiedono un codice aggiuntivo per far uscire il programma e ripristinare lo stato esattamente com'era all'inizio. Questo rende il programma un po 'più lungo e non aggiunge alcun interesse algoritmico, ma per motivi di rispetto delle regole l'ho modificato.

Programma golf

Uso di GNU Prolog perché ha una sintassi del risolutore di vincoli leggermente più corta della sintassi aritmetica di Prolog portatile, risparmiando pochi byte.

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

Questo è uno di quei problemi in cui è difficile sapere come iniziare. Non è ovvio come calcolare la forma del nodo dalla notazione data, perché non ti consente di sapere se sei destinato a piegare la linea a sinistra o a destra in una determinata posizione (e come tale, il la notazione potrebbe essere ambigua). La mia soluzione era, in effetti, quella di utilizzare il vecchio standby del golf: scrivere un programma incredibilmente inefficiente che genera tutti gli output possibili e quindi vedere se corrispondono all'input. (L'algoritmo utilizzato qui è leggermente più efficiente, in quanto Prolog può eliminare il vicolo cieco occasionale, ma non ha abbastanza informazioni per migliorare la complessità computazionale.)

L'uscita è tramite il terminale art. GNU Prolog sembra desiderare un set di caratteri a byte singolo coerente con ASCII, ma non gli interessa quale viene utilizzato (poiché tratta i caratteri con il bit alto impostato come opaco). Di conseguenza, ho usato IBM850, che è ampiamente supportato e ha i caratteri di disegno di cui abbiamo bisogno.

Produzione

Il programma cerca tutte le possibili immagini dei nodi, nell'ordine delle dimensioni del loro riquadro di selezione, quindi esce quando trova la prima. Ecco come appare l'output m([0]).:

 ┌┐
┌│┘
└┘ 

Questo ha richiesto 290.528 secondi per essere eseguito sul mio computer; il programma non è molto efficiente. L'ho lasciato in funzione per due ore m([0,1])e ho finito con questo:

┌┐┌┐
└─│┘
 └┘ 

Versione non golfata con commenti

L'evidenziatore di sintassi di Stack Exchange sembra avere il simbolo di commento errato per Prolog, quindi al posto dei %commenti (che Prolog utilizza effettivamente), questa spiegazione utilizza i % #commenti (che sono ovviamente equivalenti a causa dell'inizio %, ma evidenziati correttamente).

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

Ho scaricato il prologo GNU, ho copiato il tuo codice in un file .txt, l'ho salvato come file .pl con codifica ASCII e nella console chiamata m ([0])
J. Antonio Perez,

Esistono modi per rendere più efficiente il programma?
J. Antonio Perez,

Sospetto che il programma possa essere reso più efficiente, ma non esiste un modo ovvio o semplice. Cambiare l'ordine di valutazione per spostarsi lungo il nodo, piuttosto che da sinistra a destra / dall'alto verso il basso, probabilmente aiuterebbe, ma non sono sicuro di quanto aiuterebbe (e sarebbe anche sostanzialmente più codice, quindi non praticabile in un code-golf ).

Capisci perché sono riluttante, vero? Voglio dire ... anche il codice migliore deve essere testato e la tua soluzione è così complessa che non posso nemmeno verificare che riprodurrà il nodo più semplice (il nodo [2, 0, 1]).
J. Antonio Perez,

Ho capito, si. Tuttavia, questo è un po 'inerente al code-golf , specialmente quando si tratta di Prolog. Il codice è in realtà molto semplice, motivo per cui viene eseguito così lentamente; code-golf su un problema complesso porta quasi sempre a una soluzione a forza bruta che controlla tutti i possibili output rispetto alle specifiche. Fare qualcosa per rendere il programma più efficiente lo renderebbe sostanzialmente più lungo e molto probabilmente renderebbe più difficile la comprensione e la dimostrazione anche corretta.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.