Ein ziemlich knorriges Rätsel

23

Schreiben Sie ein Programm, um ein 2-D-Diagramm eines Knotens auf der Grundlage der Knotenstruktur zu zeichnen. Ein Knoten ist genau das, wonach es sich anhört: eine Seilschleife, die festgebunden ist. In der Mathematik zeigt ein Knotendiagramm, wo sich ein Stück Seil über oder unter sich kreuzt, um den Knoten zu bilden. Einige beispielhafte Knotendiagramme sind unten dargestellt:

Bildbeschreibung hier eingeben

Es gibt einen Bruch in der Linie, in der sich das Seil kreuzt.

Eingabe: Die Eingabe, die den Knoten beschreibt, ist ein Array von ganzen Zahlen. Ein Knoten, bei dem sich das Seil n- mal kreuzt, kann als Array von n ganzen Zahlen mit einem Wert im Bereich [0, n-1] dargestellt werden. Lassen Sie uns dieses Array nennen K .

Um das Array zu erhalten, das einen Knoten beschreibt, nummerieren Sie jedes der Segmente 0 bis n-1. Segment 0 sollte zu Segment 1 führen, was zu Segment 2 führen sollte, was zu Segment 3 führen sollte, und so weiter, bis sich Segment n-1 zurückzieht und zu Segment 0 führt. Ein Segment endet, wenn ein anderes Seilsegment darüber läuft ( durch einen Zeilenumbruch im Diagramm dargestellt). Nehmen wir den einfachsten Knoten - den Kleeblattknoten. Nachdem wir die Segmente nummeriert haben, endet Segment 0, wenn Segment 2 darüber läuft. Segment 1 endet, wenn Segment 0 darüber läuft. und Segment 2 endet, wenn Segment 1 es überquert. Somit ist das Array, das den Knoten beschreibt, [2, 0, 1]. Im allgemeinen Segment x beginnt , wo Segment x-1 mod n aufhörte und endet in dem Segment K [x] kreuzt es.

Das folgende Bild zeigt Knotendiagramme mit beschrifteten Segmenten und den entsprechenden Arrays, die den Knoten beschreiben.

Bildbeschreibung hier eingeben

Die oberen drei Diagramme sind echte Knoten, während die unteren drei Seilschlaufen sind, die sich selbst kreuzen, aber nicht wirklich geknüpft sind (aber immer noch entsprechende Codes haben).

Ihre Aufgabe ist es, eine Funktion zu schreiben, die ein Array von ganzen Zahlen K (man könnte es auch anders nennen) verwendet, die einen Knoten (oder eine Seilschleife, die nicht wirklich geknüpft ist) beschreibt und das entsprechende Knotendiagramm wie oben beschrieben erzeugt Beispiele. Wenn Sie können, geben Sie eine unbenutzte Version Ihres Codes oder eine Erklärung an und stellen Sie auch Beispielausgaben Ihres Codes bereit. Derselbe Knoten kann häufig auf mehrere verschiedene Arten dargestellt werden. Wenn jedoch das Knotendiagramm, das Ihre Funktionsausgaben erfüllen, die Eingabe als eine der möglichen Darstellungen enthält, ist Ihre Lösung gültig.

Das ist Code-Golf, also gewinnt der kürzeste Code in Bytes. Die Linie, die das Seil darstellt, kann eine Dicke von 1 Pixel haben, Unter- und Überkreuzungen müssen jedoch deutlich unterscheidbar sein (die Größe des Seilbruchs sollte um mindestens ein Pixel auf beiden Seiten größer sein als die Dicke des Seils). .

Ich werde Antworten positiv bewerten, die auf den Fähigkeiten der eingebauten Knotentheorie beruhen, aber die, die am Ende ausgewählt wurden, können sich nicht auf die Fähigkeiten der eingebauten Knotentheorie stützen.

Alles, was ich über meine Notation weiß: Ich glaube, dass es Wertesequenzen gibt, die keinem Knoten oder Knoten entsprechen. Zum Beispiel scheint es unmöglich zu sein, die Sequenz [2, 3, 4, 0, 1] zu zeichnen.

Angenommen, Sie nehmen eine Kreuzung und folgen ab dieser Kreuzung dem Pfad des Seils in eine Richtung und kennzeichnen jede nicht gekennzeichnete Kreuzung, auf die Sie stoßen, mit immer größeren Integralwerten. Für alternierende Knoten gibt es einen einfachen Algorithmus, um meine Notation in eine solche Kennzeichnung umzuwandeln, und für alternierende Knoten ist es trivial, diese Kennzeichnung in einen Gauß-Code umzuwandeln:

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;
}
J. Antonio Perez
quelle
4
Sie sollten dafür wahrscheinlich Builtins verbieten. (Zu diesem Zeitpunkt wäre ich schockiert, wenn Mathematica keine hat.)
7
@ ais523 Jetzt kann ich nicht KnotDatain Mathematica verwenden ...: '(
JungHwan Min
1
Ich bin gespannt auf die Notation, die Sie für das Knoten-Kreuzungsdiagramm verwenden. Hat es einen Namen? Ist es möglich, dass zwei nicht äquivalente Knoten dasselbe Array haben?
xnor
2
@ ais523: Mathematica hat total ein Knoteingebautes! Anwendungsbeispiel: << Units`; Convert[Knot, Mile/Hour]Erträge 1.1507794480235425 Mile/Hour. (Ich denke, das ist lustig, egal ob es wahr oder falsch ist; aber es ist tatsächlich wahr.)
Greg Martin
1
[0], [0,1], [0,1,2], [1,0] und eine Vielzahl anderer Arrays erzeugen alle "Knoten", die dem Unknot äquivalent sind, jedoch wäre das Ausgeben einer einfachen Schleife in inkorrekt einer dieser Fälle. Die Notation [0] bedeutet sehr spezifisch eine Seilschleife, die sich genau einmal schneidet, und es ist sehr leicht zu erkennen, ob eine auf dem Bildschirm gezeichnete Figur der eingegebenen Notation entspricht oder nicht.
J. Antonio Perez

Antworten:

22

GNU Prolog, 622 634 668 Bytes in Codepage 850

Update : In der vorherigen Version des Programms wurden Kreuzungen manchmal so eng, dass sie nicht richtig gerendert wurden, was gegen die Spezifikation verstößt. Ich habe zusätzlichen Code hinzugefügt, um dies zu verhindern.

Update : Anscheinend erfordern PPCG-Regeln zusätzlichen Code, damit das Programm beendet und der Status genau so wiederhergestellt wird, wie er zu Beginn war. Dies macht das Programm etwas länger und fügt ihm kein algorithmisches Interesse hinzu, aber im Interesse der Regelkonformität habe ich es geändert.

Golfprogramm

Verwendung von GNU Prolog, da es eine Constraint-Solver-Syntax hat, die etwas kürzer ist als die arithmetische Syntax von Portable Prolog, wodurch einige Bytes eingespart werden.

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

Algorithmus

Dies ist eines der Probleme, bei denen es schwierig ist, den Anfang zu machen. Es ist nicht offensichtlich, wie die Form des Knotens aus der angegebenen Notation berechnet werden kann, da Sie nicht wissen, ob Sie die Linie an einer bestimmten Stelle nach links oder rechts biegen sollen (und als solche auch die Notation kann mehrdeutig sein). Meine Lösung bestand effektiv darin, den alten Standby-Modus für das Golfen zu nutzen: Schreiben Sie ein unglaublich ineffizientes Programm, das alle möglichen Ausgaben generiert und dann prüft, ob sie mit den Eingaben übereinstimmen. (Der hier verwendete Algorithmus ist etwas effizienter, da Prolog die gelegentliche Sackgasse beseitigen kann, aber nicht über genügend Informationen verfügt, um die Rechenkomplexität zu verbessern.)

Die Ausgabe erfolgt über das Terminal Art. GNU Prolog scheint einen Einzelbyte-Zeichensatz zu wollen, der mit ASCII konsistent ist, es ist jedoch egal, welcher verwendet wird (da Zeichen mit dem hohen Bit als undurchsichtig behandelt werden). Als Ergebnis habe ich IBM850 verwendet, das weitgehend unterstützt wird und die von uns benötigten Strichzeichnungszeichen enthält.

Ausgabe

Das Programm durchsucht alle möglichen Knotenbilder in der Reihenfolge der Größe ihres Begrenzungsrahmens und wird dann beendet, wenn es den ersten findet. So sieht die Ausgabe aus m([0]).:

 ┌┐
┌│┘
└┘ 

Auf meinem Computer dauerte die Ausführung 290.528 Sekunden. Das Programm ist nicht sehr effizient. Ich ließ es zwei Stunden laufen m([0,1])und endete damit:

┌┐┌┐
└─│┘
 └┘ 

Ungolfed-Version mit Kommentaren

Der Syntax-Textmarker von Stack Exchange scheint das falsche Kommentarsymbol für Prolog zu haben, daher werden in %dieser Erklärung anstelle von Kommentaren (die von Prolog verwendet werden) % #Kommentare verwendet (die natürlich gleichbedeutend sind %, aber korrekt hervorgehoben werden).

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

quelle
Ich lud GNU Prolog herunter, kopierte Ihren Code in eine TXT-Datei, speicherte ihn als ASCII-codierte PL-Datei und in der Konsole mit dem Namen m ([0])
J. Antonio Perez
Gibt es Möglichkeiten, das Programm effizienter zu gestalten?
J. Antonio Perez
Ich vermute, dass das Programm effizienter gestaltet werden kann, aber es gibt keinen offensichtlichen oder einfachen Weg. Das Ändern der Auswertungsreihenfolge, um sich entlang des Knotens zu bewegen, anstatt von links nach rechts / von oben nach unten, würde wahrscheinlich helfen, aber ich bin nicht sicher, wie viel es helfen würde (und es wäre auch wesentlich mehr Code, also in einem Code-Golf nicht lebensfähig ).
Du verstehst, warum ich zögere, oder? Ich meine ... selbst der beste Code muss getestet werden, und Ihre Lösung ist so komplex, dass ich nicht einmal überprüfen kann, ob sie den einfachsten Knoten (den [2, 0, 1] Knoten) reproduziert.
J. Antonio Perez
Ich verstehe ja Dies ist jedoch eine Art inhärenter Code-Golf , insbesondere wenn es um Prolog geht. Der Code ist eigentlich sehr einfach, weshalb er so langsam läuft. Code-Golf bei einem komplexen Problem führt so gut wie immer zu einer Brute-Force-Lösung, die alle möglichen Ausgaben mit der Spezifikation vergleicht. Etwas zu tun, um das Programm effizienter zu machen, würde es wesentlich länger machen und es möglicherweise auch schwieriger machen, es zu verstehen und sich als richtig zu erweisen.