오히려 결절 수수께끼


23

매듭의 구조에 따라 매듭의 2 차원 다이어그램을 그리는 프로그램을 작성하십시오. 매듭은 정확히 같은 소리입니다. 묶인 밧줄 고리. 수학에서 매듭 다이어그램은 로프 조각이 매듭을 형성하기 위해 그 위 또는 아래로 교차하는 위치를 보여줍니다. 매듭 다이어그램의 예는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

줄이 끊어지면서 줄이 끊어집니다.

입력 : 매듭을 설명하는 입력은 정수 배열입니다. 로프가 n 회 이상 교차하는 매듭은 각각 [0, n-1] 범위의 값을 갖는 n 개의 정수 의 배열로 표현 될 수있다 . 이 배열 K 라고합시다 .

매듭을 설명하는 배열을 얻으려면 각 세그먼트 0에서 n-1까지 번호를 매 깁니다. 세그먼트 0은 세그먼트 1로 이어지고 세그먼트 2로 이어지고 세그먼트 3으로 이어져야합니다. 세그먼트 n-1이 다시 루프되어 세그먼트 0으로 이어질 때까지 세그먼트가 끝납니다. 다이어그램에서 줄 바꿈으로 표시). 트레 포일 매듭-가장 간단한 매듭을 보자. 세그먼트에 번호를 매기고 나면 세그먼트 2가 세그먼트 위로 교차하면 세그먼트 0이 종료됩니다. 세그먼트 1이 세그먼트 0을 넘어 서면 세그먼트 1이 종료됩니다. 세그먼트 2가 세그먼트 1을 지나갈 때 세그먼트 2가 종료됩니다. 따라서 매듭을 설명하는 배열은 [2, 0, 1]입니다. 일반적으로, 세그먼트 x 는 세그먼트 x-1 mod n 이 중단 된 곳에서 시작하고 세그먼트 K [x] 가 그 위에서 교차하는 곳에서 끝납니다 .

아래 이미지는 레이블이 지정된 세그먼트와 매듭을 설명하는 해당 배열이있는 매듭 다이어그램을 보여줍니다.

여기에 이미지 설명을 입력하십시오

맨 위 세 개의 다이어그램은 실제 매듭이고, 맨 아래 세 개는 자체적으로 교차하지만 실제로는 매듭되지 않은 로프 루프입니다 (그러나 여전히 해당 코드가 있음).

당신의 임무는 매듭 (또는 실제로 매듭되지 않은 로프의 루프)을 설명하고 위의 설명과 같이 해당 매듭 다이어그램을 생성하는 정수 K 배열 (다른 것으로 부를 수 있음) 을 취하는 함수를 작성하는 것입니다. 예. 가능하면 코드의 압축되지 않은 버전이나 설명을 제공하고 코드의 샘플 출력도 제공하십시오. 동일한 매듭을 여러 가지 방법으로 표현할 수 있지만 함수 출력이 만족하는 매듭 다이어그램에 가능한 표현 중 하나로 입력이 있으면 솔루션이 유효합니다.

이것은 코드 골프이므로 바이트 단위의 가장 짧은 코드가 이깁니다. 로프를 나타내는 선은 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 이제 KnotDataMathematica에서 사용할 수 없습니다 ... : '(
JungHwan Min

1
매듭 교차 다이어그램에 사용하는 표기법이 궁금합니다. 이름이 있습니까? 두 개의 동일하지 않은 매듭이 동일한 배열을 가질 수 있습니까?
xnor

2
@ ais523 : Mathematica는 완전히 Knot내장되어 있습니다! 사용법 예 : << Units`; Convert[Knot, Mile/Hour]yields 1.1507794480235425 Mile/Hour. (나는 이것이 사실이든 아니든 관계없이 이것이 재미 있다고 생각하지만 실제로는 사실이다.)
Greg Martin

1
[0], [0,1], [0,1,2], [1,0] 및 기타 여러 배열은 모두 unknot과 동등한 "노트"를 생성하지만 간단한 루프 출력은 올바르지 않습니다. 그러한 경우 표기법 [0]은 구체적으로 정확히 한 번 교차하는 로프 루프를 의미하며, 화면에 그려진 그림이 입력 표기법을 만족하는지 여부를 쉽게 알 수 있습니다.
J. Antonio Perez

답변:


22

GNU 도입부 622 634 코드 페이지 850에서 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).

연산

이것은 시작하는 방법을 알기가 어려운 문제 중 하나입니다. 주어진 표기법에서 매듭의 모양을 해결하는 방법은 확실하지 않습니다. 표기법이 모호 할 수 있습니다). 내 솔루션은 효과적으로 오래된 골프 대기를 사용하는 것입니다. 가능한 모든 출력을 생성하고 입력과 일치하는지 확인하는 매우 비효율적 인 프로그램을 작성하십시오. (Prolog가 가끔 막 다른 골목을 버릴 수 있기 때문에 여기에서 사용되는 알고리즘이 약간 더 효율적이지만, 계산 복잡성을 향상 시키기에는 충분한 정보가 없습니다.)

출력은 터미널 아트를 통해 이루어집니다. GNU Prolog는 ASCII와 일치하는 단일 바이트 문자 세트를 원하지만 어떤 비트가 사용되는지는 상관하지 않습니다 (높은 비트 세트를 가진 문자를 불투명으로 처리하므로). 결과적으로 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

GNU 프롤로그를 다운로드하고 코드를 .txt 파일로 복사 한 후 ascii로 인코딩 된 .pl 파일로 저장하고 m ([0])이라는 콘솔에 저장했습니다.
J. Antonio Perez

프로그램을보다 효율적으로 만들 수있는 방법이 있습니까?
J. Antonio Perez

프로그램을 더 효율적으로 만들 수있을 것 같지만 분명하거나 쉬운 방법은 없습니다. 왼쪽에서 오른쪽 / 위에서 아래로가 아니라 매듭을 따라 이동하도록 평가 순서를 변경하면 도움이 될지 모르지만 그것이 얼마나 도움이 될지 확신하지 못합니다 (그리고 실제로 더 많은 코드가 필요합니다. 따라서 code-golf 에서 실행 가능하지 않습니다 ).

내가 왜 꺼리는지 알 겠지? 내 말은 ... 가장 좋은 코드조차도 테스트해야하며, 솔루션이 너무 복잡해서 가장 간단한 매듭 ([2, 0, 1] 매듭)을 재현하는지 확인할 수도 없습니다.
J. Antonio Perez

이해합니다 그러나 이것은 특히 Prolog와 관련하여 code-golf 고유의 것입니다 . 코드는 실제로 매우 단순하기 때문에 느리게 실행됩니다. 복잡한 문제에 대한 코드 골프 는 거의 항상 사양에 대해 가능한 모든 출력을 확인하는 무차별 대입 솔루션으로 이어집니다. 프로그램을보다 효율적으로 만들기 위해 무언가를하면 실질적으로 더 길어질 수 있으며, 이해하고 올바르게 이해하기가 더 어려워 질 수도 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.