Oldukça Düğümlü Conundrum


23

Düğüm yapısına dayanarak bir düğümün 2-D diyagramını çizmek için bir program yazın. Bir düğüm tam olarak neye benzediği gibidir: bağlanmış bir ip ipi. Matematikte, bir düğüm diyagramı düğümü oluşturmak için bir ip parçasının kendisinin üstünden veya altından geçtiği yerleri göstermektedir. Bazı örnek düğüm diyagramları aşağıda gösterilmiştir:

görüntü tanımını buraya girin

İpin kendi üzerinden geçtiği çizgide bir kopma var.

Giriş: Düğümü tanımlayan giriş bir tam sayı dizisidir. Halatın n defa kendisinin üzerinden geçtiği bir düğüm , her biri [0, n-1] aralığında bir değere sahip olan bir n tamsayı dizisi olarak gösterilebilir . Bu diziyi K diyelim .

Bir düğümü tanımlayan diziyi elde etmek için, 0'dan n-1'e kadar olan bölümlerin her birini numaralandırın. 0 segmenti, segment 2'ye yol açması gereken segment 2'ye, segment 3'e yol açmalı ve segment n-1 geri dönene ve segmenti 0'a gelene kadar devam etmelidir. diyagramdaki çizgide bir mola ile gösterilir). En basit düğümü alalım - trefoil düğümü. Segmentleri numaralandırdıktan sonra, segment 2 üstünden geçtiğinde segment 0 biter; bölüm 1 üzerinden geçtiğinde bölüm 1 sona erer; ve segment 2, segment 1 üzerinden geçtiğinde biter. Böylece, düğümü tanımlayan dizi [2, 0, 1] 'dir. Genel olarak, x kesimi, x-1 kesiminin n bıraktığı yerde başlar ve K [x] kesiminin üstünden geçtiği yerde biter .

Aşağıdaki resim, etiketli bölümlere ve düğümü tanımlayan ilgili dizilere sahip düğüm şemalarını göstermektedir.

görüntü tanımını buraya girin

En üstteki üç diyagram gerçek düğümlerdir, alttaki üç ise üzerlerinden geçen ancak gerçekte düğümlenmemiş (ancak buna karşılık gelen kodlara sahip olan) ip halkalarıdır.

Göreviniz Sayı dizisi alır bir işlev yazmaktır K (bunu farklı bir şey diyebiliriz) bir düğüm (veya aslında düğümlü değil ipin döngü) açıklayan ve yukarıda açıklandığı gibi, ilgili düğüm diyagramı üretir hangi örnekleri. Yapabiliyorsanız, kodunuzun eski bir sürümünü veya bir açıklamasını sağlayın ve ayrıca kodunuzun örnek çıktılarını sağlayın. Aynı düğüm çoğu zaman farklı şekillerde gösterilebilir, ancak işlev çıktısı veren düğüm şemasının girişini olası gösterimlerinden biri olarak kabul etmesi durumunda çözümünüz geçerlidir.

Bu kod golf, bayt cinsinden en kısa kod kazanır. İpi temsil eden çizgi 1 piksellik bir kalınlığa sahip olabilir, ancak alt ve üst geçişler açıkça ayırt edilebilir olmalıdır (ipteki kopma boyutu ipin kalınlığından en az iki tarafın bir pikselinden büyük olmalıdır) .

Yerleşik düğüm teorisi yeteneklerine dayanan cevapları değiştireceğim, fakat sonunda seçilen cevap, yerleşik düğüm teorisi yeteneklerine dayanamaz.

İşaretlemem hakkında bildiğim her şey: Herhangi bir düğüme veya bilmediğine karşılık gelmeyen değerler dizileri olduğuna inanıyorum. Örneğin, [2, 3, 4, 0, 1] dizisinin çizilmesi imkansız gibi görünüyor.

Bunun dışında, bir geçit aldığınızı ve bu geçitten başlayarak, ipin yolunu bir yönde takip ettiğinizi ve başarılı bir şekilde daha büyük integral değerleri ile karşılaştığınız etiketlenmemiş her geçidi etiketlediğinizi varsayalım. Değişken düğümler için, gösterimimi böyle bir etiketlemeye dönüştürecek basit bir algoritma vardır ve değişken düğümler için bu etiketi Gauss Koduna dönüştürmek çok önemlidir:

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
Muhtemelen bunu yapmak için yerleşikleri yasaklamalısın. (Bu noktada, Mathematica'da bir tane yoksa şok olurum .)

7
@ ais523 Şimdi KnotDataMathematica'da kullanamıyorum ...: '(
JungHwan Min

1
Düğüm geçiş şeması için kullandığınız notasyonu merak ediyorum. Bir adı var mı? Eşdeğer olmayan iki düğümün aynı diziye sahip olması mümkün mü?
xnor

2
@ ais523: Mathematica'da tamamen Knotyerleşik bir yapı var! Örnek kullanım: << Units`; Convert[Knot, Mile/Hour]verim 1.1507794480235425 Mile/Hour. (Bunun doğru ya da yanlış olmasına bakılmaksızın komik olduğunu düşünüyorum; ama aslında doğru.)
Greg Martin

1
[0], [0,1], [0,1,2], [1,0] ve diğer çeşitli dizilerin tümü, unknot'a eşdeğer "düğümler" üretir, ancak basit bir döngü çıktısı yanlış olabilir. bu davalardan herhangi biri. Gösterim [0] çok özel olarak kendini bir kez tam olarak kesişen bir ip halkası anlamına gelir ve ekrana çizilen bir rakamın giriş gösterimini sağlayıp sağlamadığını anlamak çok kolaydır.
J. Antonio Perez

Yanıtlar:


22

GNU Prolog, kod sayfası 850'de 622 634 668 bayt

Güncelleme : Programın önceki sürümü bazen düzgün işlemeyecekleri kadar sıkı geçmeler yapıyordu ve bu da spekülasyona aykırıydı. Bunu önlemek için ekstra bir kod ekledim.

Güncelleme : Görünüşe göre PPCG kuralları, programdan çıkıp durumu tam olarak başlangıçta olduğu gibi geri yüklemek için ekstra kod gerektirir. Bu, programı biraz daha uzun hale getirir ve algoritmik bir ilgi alanı yaratmaz, ancak kurallara uygunluk çıkarlarına göre, onu değiştirdim.

Golf programı

GNU Prolog kullanarak, taşınabilir Prolog'un aritmetik sözdiziminden biraz daha kısa olan ve birkaç byte tasarruf sağlayan kısıtlayıcı bir çözümleme sözdizimine sahip.

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

Algoritma

Bu, nasıl başlayacağınızı bilmenin zor olduğu sorunlardan biridir. Düğüm şeklini verilen notasyondan nasıl çalıştıracağınız açık değildir, çünkü çizgiyi sola veya sağa doğru herhangi bir yerde bükmek isteyip istemediğinizi bilmez. gösterim belirsiz olabilir). Benim çözümüm etkili bir şekilde eski golf beklemesini kullanmaktı: mümkün olan tüm çıktıları üreten inanılmaz bir verimsiz program yazıp girdiyle uyuşup uyuşmadıklarına bakın. (Prolog ara sıra çıkmazı atabildiğinden burada kullanılan algoritma biraz daha etkilidir, ancak hesaplama karmaşıklığını iyileştirmek için yeterli bilgiye sahip değildir.)

Çıkış terminal resmi üzerindendir. GNU Prolog ASCII ile tutarlı olan tek bir bayt karakter kümesi istiyor gibi görünüyor, fakat hangisinin kullanıldığını önemsemiyor (karakterleri opak olarak ayarlanmış yüksek bitli karakterlere benzettiği için). Sonuç olarak, yaygın olarak desteklenen ve ihtiyaç duyduğumuz çizgi çizme karakterlerine sahip olan IBM850'yi kullandım.

Çıktı

Program, tüm olası düğüm resimlerini, sınırlayıcı kutularının boyutlarına göre arar, sonra ilk bulduğu zaman çıkar. Çıktının neye benzediği m([0]).:

 ┌┐
┌│┘
└┘ 

Bu bilgisayarımda çalıştırmak için 290.528 saniye sürdü; program çok verimli değil. İki saat açık m([0,1])kalmasını sağladım ve bununla sonuçlandım:

┌┐┌┐
└─│┘
 └┘ 

Yorumlanmamış Ungolfed versiyonu

Yığın Exchange'in sözdizimi vurgulayıcısı, Prolog için yanlış yorum sembolüne sahip gibi görünüyor, bu nedenle %yorumlar (Prolog'un gerçekte kullandığı) yerine, bu açıklama % #yorumları kullanır (elbette başlangıçtan başlayarak eşdeğer olan %ancak doğru bir şekilde vurgulayın).

% # 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 prologunu indirdim, kodunuzu bir .txt dosyasına kopyaladım, ascii tarafından kodlanmış bir .pl dosyası olarak ve konsolu m ([0])
J. Antonio Perez

Programı daha verimli hale getirmenin herhangi bir yolu var mı?
J. Antonio Perez

Programın daha verimli yapılabileceğinden şüpheleniyorum, ancak açık veya kolay bir yolu yok. Değerlendirme sırasını düğüm boyunca ilerlemek için, soldan sağa / yukarıdan aşağıya doğru değiştirmek, muhtemelen yardımcı olacaktır, ancak ne kadar yardımcı olacağından emin değilim (ve ayrıca daha fazla kod olacağını, bu nedenle bir kod-golf geçerli değildir ).

Neden isteksiz olduğumu anlıyorsun, değil mi? Yani ... en iyi kodun bile test edilmesi gerekiyor ve çözümünüz o kadar karmaşık ki, en basit düğümü ([2, 0, 1] düğüm) ürettiğini bile doğrulayamıyorum.
J. Antonio Perez

Anladım, evet. Bununla birlikte, bu özellikle kod yazarken, özellikle Prolog söz konusu olduğunda doğaldır. Kod aslında çok basittir, bu yüzden yavaş çalışmasının nedeni budur; Karmaşık bir problemde kod golfü , hemen hemen her zaman olası tüm çıktıları spesifikasyona göre kontrol eden kaba kuvvet çözümüne yol açar. Programı daha verimli hale getirmek için bir şeyler yapmak, programı daha uzun sürebilir ve muhtemelen doğru şekilde anlaşılmasını ve ispatlanmasını zorlaştırabilir.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.