Một câu hỏi hóc búa


23

Viết chương trình vẽ sơ đồ 2 chiều của nút thắt dựa trên cấu trúc của nút thắt. Một nút thắt chính xác là những gì nó nghe như: một vòng dây được buộc lại. Trong toán học, một sơ đồ nút cho thấy một đoạn dây vắt qua hoặc dưới chính nó để tạo thành nút thắt. Một số ví dụ sơ đồ nút được hiển thị dưới đây:

nhập mô tả hình ảnh ở đây

Có một sự phá vỡ trong dòng nơi sợi dây vượt qua chính nó.

Đầu vào: đầu vào mô tả nút là một mảng các số nguyên. Một nút thắt trong đó sợi dây đi qua chính nó n lần có thể được biểu diễn dưới dạng một mảng gồm n số nguyên, mỗi số có một giá trị trong phạm vi [0, n-1]. Hãy gọi mảng này K .

Để lấy mảng mô tả một nút thắt, hãy đánh số từng đoạn từ 0 đến n-1. Phân đoạn 0 sẽ dẫn đến phân khúc 1, dẫn đến phân khúc 2, dẫn đến phân khúc 3, và cứ thế cho đến khi phân đoạn n-1 quay trở lại và dẫn đến phân khúc 0. Một phân đoạn kết thúc khi một phân đoạn dây khác đi qua nó ( đại diện bởi một ngắt trong dòng trong sơ đồ). Chúng ta hãy lấy nút thắt đơn giản nhất - nút thắt. Sau khi chúng tôi đánh số các phân số, phân đoạn 0 kết thúc khi phân đoạn 2 vượt qua nó; đoạn 1 kết thúc khi đoạn 0 đi qua nó; và đoạn 2 kết thúc khi đoạn 1 đi qua nó. Do đó, mảng mô tả nút thắt là [2, 0, 1]. Nói chung, phân đoạn x bắt đầu khi phân đoạn x-1 mod n rời khỏi và kết thúc khi phân đoạn K [x] đi qua nó.

Hình ảnh dưới đây cho thấy sơ đồ nút thắt, với các phân đoạn được gắn nhãn và các mảng tương ứng mô tả nút thắt.

nhập mô tả hình ảnh ở đây

Ba sơ đồ trên cùng là các nút thắt thực sự, trong khi ba biểu đồ phía dưới là các vòng dây vắt ngang qua chính chúng nhưng không thực sự thắt nút (nhưng vẫn có mã tương ứng).

Nhiệm vụ của bạn là viết một hàm lấy một mảng các số nguyên K (bạn có thể gọi nó là một số khác) mô tả một nút (hoặc vòng dây không thực sự thắt nút) và tạo ra sơ đồ nút tương ứng, như được mô tả ở trên ví dụ. Nếu bạn có thể, hãy cung cấp một phiên bản không mã hóa của mã của bạn hoặc một lời giải thích, đồng thời cung cấp các đầu ra mẫu của mã của bạn. Cùng một nút thường có thể được biểu diễn theo nhiều cách khác nhau, nhưng nếu sơ đồ nút mà đầu ra chức năng của bạn thỏa mãn có đầu vào là một trong những cách biểu diễn có thể, thì giải pháp của bạn là hợp lệ.

Đây là mã golf, vì vậy mã ngắn nhất tính bằng byte thắng. Đường biểu thị cho sợi dây có thể có độ dày 1 pixel, tuy nhiên, dưới và chéo phải được phân biệt rõ ràng (kích thước của đứt dây phải lớn hơn độ dày của dây ít nhất là một pixel ở hai bên) .

Tôi sẽ đưa ra các câu trả lời dựa trên các khả năng lý thuyết nút tích hợp, nhưng câu trả lời cuối cùng được chọn không thể dựa vào các khả năng lý thuyết nút tích hợp.

Tất cả mọi thứ tôi biết về ký hiệu của mình: Tôi tin rằng có những chuỗi giá trị dường như không tương ứng với bất kỳ nút thắt hoặc nút thắt nào. Ví dụ, chuỗi [2, 3, 4, 0, 1] dường như không thể vẽ.

Ngoài ra, giả sử rằng bạn đi qua và bắt đầu từ giao cắt đó, đi theo con đường của sợi dây theo một hướng và gắn nhãn cho mọi giao cắt không được gắn nhãn mà bạn gặp phải với các giá trị tích phân lớn hơn. Đối với các nút thắt xen kẽ, có một thuật toán đơn giản để chuyển đổi ký hiệu của tôi thành nhãn như vậy và đối với các nút thắt xen kẽ, việc chuyển đổi nhãn này thành Mã Gauss là không quan trọng

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
Bạn có lẽ nên cấm xây dựng để làm điều này. (Tại thời điểm này, tôi sẽ bị sốc nếu Mathematica không có.)

7
@ ais523 Bây giờ tôi không thể sử dụng KnotDatatrong Mathematica ...: '(
JungHwan Min

1
Tôi tò mò về ký hiệu bạn sử dụng cho sơ đồ nút thắt. Nó có tên không? Có thể cho hai nút thắt không tương đương có cùng một mảng?
xnor

2
@ ais523: Mathicala hoàn toàn có Knotsẵn! Ví dụ sử dụng: << Units`; Convert[Knot, Mile/Hour]sản lượng 1.1507794480235425 Mile/Hour. (Tôi nghĩ điều này thật buồn cười bất kể nó đúng hay sai; nhưng nó thực sự đúng.)
Greg Martin

1
[0], [0,1], [0,1,2], [1,0] và một loạt các mảng khác đều tạo ra các "nút thắt" tương đương với nút thắt, tuy nhiên việc xuất ra một vòng lặp đơn giản sẽ không chính xác trong bất kỳ trường hợp nào. Ký hiệu [0] rất cụ thể có nghĩa là một vòng dây tự giao nhau chính xác một lần và rất dễ dàng để biết liệu một hình vẽ trên màn hình có thỏa mãn ký hiệu đầu vào hay không.
J. Antonio Perez

Câu trả lời:


22

GNU Prolog, 622 634 668 byte trong mã trang 850

Cập nhật : Phiên bản trước của chương trình đôi khi tạo ra các giao cắt chặt chẽ đến mức chúng không hiển thị đúng, vi phạm thông số kỹ thuật. Tôi đã thêm vào một số mã bổ sung để ngăn chặn điều đó.

Cập nhật : Rõ ràng các quy tắc PPCG yêu cầu thêm mã để làm cho chương trình thoát và khôi phục trạng thái chính xác như lúc ban đầu. Điều này làm cho chương trình dài hơn một chút và không có sự quan tâm về thuật toán đối với nó, nhưng vì lợi ích của việc tuân thủ quy tắc, tôi đã thay đổi nó.

Chương trình chơi gôn

Sử dụng GNU Prolog vì nó có cú pháp bộ giải ràng buộc ngắn hơn một chút so với cú pháp số học của Prolog di động, tiết kiệm một vài 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).

Thuật toán

Đây là một trong những vấn đề khó mà biết bắt đầu như thế nào. Không rõ ràng làm thế nào để tìm ra hình dạng của nút thắt từ ký hiệu đã cho, bởi vì nó không cho bạn biết bạn có ý định uốn đường bên trái hay bên phải tại bất kỳ vị trí nào (và như vậy, ký hiệu có thể mơ hồ). Giải pháp của tôi là, một cách hiệu quả, sử dụng chế độ chờ chơi golf cũ: viết một chương trình cực kỳ kém hiệu quả, tạo ra tất cả các đầu ra có thể và sau đó xem liệu chúng có khớp với đầu vào không. (Thuật toán được sử dụng ở đây hiệu quả hơn một chút, vì Prolog có thể loại bỏ ngõ cụt không thường xuyên, nhưng nó không có đủ thông tin để cải thiện độ phức tạp tính toán.)

Đầu ra là thông qua nghệ thuật thiết bị đầu cuối. GNU Prolog dường như muốn có một bộ ký tự byte đơn phù hợp với ASCII, nhưng không quan tâm cái nào được sử dụng (vì nó xử lý các ký tự có bit cao được đặt thành mờ). Kết quả là, tôi đã sử dụng IBM850, được hỗ trợ rộng rãi và có các ký tự vẽ đường mà chúng ta cần.

Đầu ra

Chương trình tìm kiếm tất cả các hình ảnh nút có thể, theo thứ tự kích thước của khung giới hạn của chúng, sau đó thoát ra khi tìm thấy cái đầu tiên. Đây là những gì đầu ra trông như thế nào cho m([0]).:

 ┌┐
┌│┘
└┘ 

Điều này mất 290.528 giây để chạy trên máy tính của tôi; chương trình không hiệu quả lắm. Tôi đã để nó chạy trong hai giờ m([0,1])và kết thúc với điều này:

┌┐┌┐
└─│┘
 └┘ 

Phiên bản Ungolfed với ý kiến

Cú pháp tô sáng của Stack Exchange dường như có ký hiệu nhận xét sai cho Prolog, vì vậy thay vì %nhận xét (mà Prolog thực sự sử dụng), giải thích này sử dụng các % #nhận xét (tất nhiên là tương đương do bắt đầu bằng %, nhưng đánh dấu chính xác).

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

Tôi đã tải xuống GNU prolog, sao chép mã của bạn vào tệp .txt, lưu nó dưới dạng tệp .pl được mã hóa ascii và trong bảng điều khiển có tên m ([0])
J. Antonio Perez

Có cách nào bạn có thể làm cho chương trình hiệu quả hơn không?
J. Antonio Perez

Tôi nghi ngờ chương trình có thể được thực hiện hiệu quả hơn, nhưng không có cách rõ ràng hoặc dễ dàng. Thay đổi thứ tự đánh giá để di chuyển dọc theo nút thắt, thay vì từ trái sang phải / từ trên xuống dưới, có thể sẽ giúp ích, nhưng tôi không chắc nó sẽ giúp được bao nhiêu (và nó cũng sẽ có nhiều mã hơn, do đó không khả thi trong một mã golf ).

Bạn hiểu tại sao tôi miễn cưỡng, phải không? Ý tôi là ... ngay cả mã tốt nhất cũng cần được kiểm tra và giải pháp của bạn phức tạp đến mức tôi thậm chí không thể xác minh rằng nó sẽ tái tạo nút thắt đơn giản nhất (nút [2, 0, 1]).
J. Antonio Perez

Tôi hiểu, vâng. Tuy nhiên, đây là loại vốn có trong môn đánh gôn , đặc biệt là khi nói đến Prolog. Mã này thực sự rất đơn giản, đó là lý do nó chạy rất chậm; code-golf về một vấn đề phức tạp khá nhiều luôn dẫn đến một giải pháp vũ phu kiểm tra tất cả các đầu ra có thể so với đặc điểm kỹ thuật. Làm một cái gì đó để làm cho chương trình hiệu quả hơn sẽ làm cho nó dài hơn đáng kể, và hoàn toàn có thể làm cho nó khó hiểu và chứng minh chính xác hơn.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.