むしろ厄介な難問


23

ノットの構造に基づいてノットの2次元図を描くプログラムを作成します。結び目はまさにそのように聞こえます:縛られたロープのループ。数学では、結び目図は、ロープがそれ自体の上または下を横切って結び目を形成する場所を示します。いくつかのノットダイアグラムの例を以下に示します。

ここに画像の説明を入力してください

ロープがそれ自体を横切るラインにブレークがあります。

入力:結び目を記述する入力は整数の配列です。ロープがそれ自体をn回横切るノットは、それぞれが範囲[0、n-1]の値を持つn個の整数の配列として表すことができます。この配列をKと呼びましょう。

ノットを記述する配列を取得するには、セグメント0〜n-1のそれぞれに番号を付けます。セグメント0は、セグメント1につながり、セグメント2につながり、セグメント3につながり、セグメントn-1がループバックしてセグメント0につながるまで続きます。ロープの別のセグメントが交差するとセグメントは終了します(図の線の切れ目で表されます)。最も単純な結び目、トレフォイル結び目を取りましょう。セグメントに番号を付けた後、セグメント0はセグメント2が交差すると終了します。セグメント1は、セグメント0が交差すると終了します。セグメント1は、セグメント1が横切ると終了します。したがって、結び目を記述する配列は[2、0、1]です。一般に、セグメントxは、セグメントx-1 mod nが中断したところから始まり、セグメントK [x]が交差するところで終わります。

次の図は、ラベル付きのセグメントと、結び目を説明する対応する配列を持つ結び目図を示しています。

ここに画像の説明を入力してください

上の3つの図は真の結び目であり、下の3つの図はそれ自体を横切るが実際には結び付けられていない(ただし対応するコードがまだある)ロープのループです。

あなたの仕事は、上記のように、結び目(または実際には結び目ではないロープのループ)を記述する整数Kの配列(実際には結び付けられていないロープのループ)を受け取る関数を作成することです例。可能な場合は、コードの未バージョンまたは説明を提供し、コードのサンプル出力も提供します。多くの場合、同じノットは複数の異なる方法で表すことができますが、関数の出力が満たすノットダイアグラムの入力が可能な表現の1つである場合、ソリューションは有効です。

これはコードゴルフなので、バイト単位の最短コードが優先されます。ロープを表す線の太さは1ピクセルにすることができますが、アンダークロッシングとオーバークロッシングは明確に区別できる必要があります(ロープの切れ目のサイズは、ロープの太さよりも両側で少なくとも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
おそらくこれを行うためにビルトインを禁止すべきです。(この時点で、Mathematicaにない場合ショックを受けるでしょう。)

7
@ ais523今KnotDataではMathematicaで使用できません...: '(
JungHwan Min

1
結び目交差図に使用する表記法に興味があります。名前はありますか?2つの等しくないノットが同じ配列を持つことは可能ですか?
XNOR

2
@ ais523:Mathematicaには完全にビルトインがありKnotます!使用例:<< Units`; Convert[Knot, Mile/Hour]yields 1.1507794480235425 Mile/Hour。(これは本当か偽かに関係なく面白いと思いますが、実際は本当です。)
グレッグ・マーティン

1
[0]、[0,1]、[0,1,2]、[1,0]およびその他のさまざまな配列はすべて、unknotと同等の「ノット」を生成しますが、単純なループの出力はそれらのケースのいずれか。表記[0]は、厳密に1回だけ交差するロープのループを意味し、画面に描かれた図形が入力表記を満たすかどうかを確認するのは非常に簡単です。
J.アントニオペレス

回答:


22

GNU Prolog、コードページ850で622 634 668バイト

更新:以前のバージョンのプログラムでは、交差がきつく作られていて、適切にレンダリングされないことがあり、仕様に違反していました。それを防ぐために追加のコードを追加しました。

更新:どうやら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).

アルゴリズム

これは、開始方法を知るのが難しい問題の1つです。与えられた表記法からノットの形状をどのように計算するかは明らかではありません。なぜなら、与えられた位置で線を左に曲げるか右に曲げるかを知らないからです。表記があいまいになる場合があります)。私の解決策は、古いゴルフスタンバイを効果的に使用することでした。可能なすべての出力を生成し、入力と一致するかどうかを確認する非常に非効率なプログラムを作成します。(ここで使用されるアルゴリズムは、Prologが時折行き止まりになる可能性があるため、わずかに効率的ですが、計算の複雑さを改善するのに十分な情報がありません。)

出力はターミナルアート経由です。GNU Prologは、ASCIIと整合性のある1バイト文字セットを必要としているように見えますが、どちらが使用されるかは気にしません(高ビットセットの文字を不透明として扱うため)。その結果、IBM850を使用しました。IBM850は広くサポートされており、必要な線画文字を備えています。

出力

プログラムは、バウンディングボックスのサイズの順にすべての可能な結び目画像を検索し、最初に見つかったときに終了します。出力は次のようになりm([0]).ます。

 ┌┐
┌│┘
└┘ 

これは私のコンピューターで実行するのに290.528秒かかりました。プログラムはあまり効率的ではありません。で2時間実行したままにしてm([0,1])、次のようになりました。

┌┐┌┐
└─│┘
 └┘ 

コメント付きの未ゴルフバージョン

Stack Exchangeの構文ハイライターには、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

Iは、.txtファイルにコードをコピーし、GNUプロローグをダウンロードASCIIエンコード.plのファイルとして保存され、コンソールにM([0])と呼ばれる
J.アントニオペレス

プログラムをより効率的にする方法はありますか?
J.アントニオペレス

プログラムをより効率的にすることができると思いますが、明白または簡単な方法はありません。評価順序を左から右/上から下ではなく結び目に沿って移動するように変更すると役立つ可能性が高くなりますが、それがどれだけ役立つかわかりません(また、かなり多くのコードになるでしょう、したがって、コードゴルフでは実行できません)。

なぜ私が嫌がるのか理解していますか?つまり、最高のコードでさえテストする必要があり、ソリューションは非常に複雑であるため、非常に単純なノット([2、0、1]ノット)を再現できるかどうかを検証することさえできません。
J.アントニオペレス

はい、わかりました。ただし、これは、特にPrologに関しては、code-golfに固有のものです。コードは実際には非常に単純であるため、実行が非常に遅くなります。複雑な問題に対するcode-golfは、ほとんどの場合、仕様に対してすべての可能な出力をチェックするブルートフォースソリューションにつながります。プログラムをより効率的にするために何かを行うと、プログラムが大幅に長くなり、正しいことを理解して証明することも難しくなります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.