Conundrum ค่อนข้างยากที่จะแก้ไข


23

เขียนโปรแกรมเพื่อวาดแผนภาพ 2 มิติของปมโดยขึ้นอยู่กับโครงสร้างของปม ปมคือสิ่งที่ฟังดูเหมือน: เป็นเชือกที่ผูกไว้ ในวิชาคณิตศาสตร์แผนภาพปมแสดงให้เห็นว่าชิ้นส่วนของเชือกข้ามหรือใต้ตัวเองเพื่อสร้างปม ตัวอย่างไดอะแกรมแสดงปมด้านล่าง:

ป้อนคำอธิบายรูปภาพที่นี่

มีตัวแบ่งในบรรทัดที่เชือกข้ามตัวเองได้

อินพุต: อินพุตที่อธิบายถึงปมคืออาร์เรย์ของจำนวนเต็ม ปมที่เชือกข้ามตัวมันเองnครั้งสามารถแสดงเป็นอาร์เรย์ของจำนวนเต็มnจำนวนโดยแต่ละค่าในช่วง [0, n-1] ขอเรียกอาร์เรย์นี้K

ในการรับอาร์เรย์ที่อธิบายถึงปมให้ระบุหมายเลขแต่ละเซ็กเมนต์ 0 ถึง n-1 ส่วนที่ 0 ควรนำไปสู่ส่วนที่ 1 ซึ่งควรนำไปสู่ส่วนที่ 2 ซึ่งควรนำไปสู่ส่วนที่ 3 และต่อ ๆ ไปจนกว่าส่วนที่ n-1 จะวนกลับมาและนำไปสู่ส่วนที่ 0 ส่วนจะสิ้นสุดลงเมื่อส่วนอื่น ๆ แสดงโดยตัวแบ่งในบรรทัดในแผนภาพ) ลองมาหาเงื่อนที่ง่ายที่สุด - ปมพระฉายาลักษณ์ หลังจากที่เราได้นับเซกเมนต์แล้วเซกเมนต์ 0 จะสิ้นสุดลงเมื่อเซกเมนต์ 2 ตัดกัน ส่วนที่ 1 สิ้นสุดลงเมื่อส่วนที่ 0 ข้ามไป และส่วนที่ 2 สิ้นสุดลงเมื่อส่วนที่ 1 ตัดผ่าน ดังนั้นอาร์เรย์ที่อธิบายถึงปมคือ [2, 0, 1] โดยทั่วไปเซ็กเมนต์xเริ่มต้นโดยที่เซ็กเมนต์x-1 mod n ค้างไว้และสิ้นสุดที่ส่วนK [x]ตัดกัน

ภาพด้านล่างแสดงไดอะแกรมโบว์พร้อมเซ็กเมนต์ที่มีเลเบลและอาร์เรย์ที่สอดคล้องซึ่งอธิบายถึงปม

ป้อนคำอธิบายรูปภาพที่นี่

แผนภาพสามอันดับแรกเป็นนอตจริงในขณะที่สามด้านล่างเป็นลูปของเชือกที่ข้ามตัวเอง แต่ไม่ได้ผูกปมจริง ๆ (แต่ยังมีรหัสที่เกี่ยวข้อง)

งานของคุณคือการเขียนฟังก์ชั่นที่ใช้อาร์เรย์ของจำนวนเต็มK (คุณสามารถเรียกมันว่าอะไรบางอย่างที่แตกต่างกัน) ที่อธิบายถึงปม (หรือห่วงของเชือกที่ไม่ได้ผูกปมไว้) ตัวอย่าง. หากเป็นไปได้ให้ระบุรหัสหรือคำอธิบายในเวอร์ชันที่ไม่ดีและให้ผลลัพธ์ตัวอย่างของโค้ดของคุณ ปมเดียวกันสามารถแสดงได้หลายวิธี แต่ถ้าปมไดอะแกรมฟังก์ชันที่เอาต์พุตของคุณตรงกับอินพุตเป็นหนึ่งในวิธีที่เป็นไปได้วิธีแก้ปัญหาของคุณจะใช้ได้

นี่คือ code-golf ดังนั้นรหัสที่สั้นที่สุดในหน่วยไบต์ชนะ เส้นที่เป็นตัวแทนของเชือกสามารถมีความหนา 1 พิกเซลอย่างไรก็ตามภายใต้และข้ามแยกจะต้องแยกแยะได้อย่างชัดเจน (ขนาดของการแตกในเชือกควรมากกว่าความหนาของเชือกอย่างน้อยหนึ่งพิกเซลด้านใดด้านหนึ่ง) .

ฉันจะถอนคำตอบที่ต้องพึ่งพาความสามารถของทฤษฎีปมในตัว แต่สิ่งที่เลือกไว้ในท้ายที่สุดไม่สามารถพึ่งพาความสามารถในทฤษฎีปมในตัวได้

ทุกสิ่งที่ฉันรู้เกี่ยวกับสัญกรณ์ของฉัน:ฉันเชื่อว่ามีลำดับของค่าที่ดูเหมือนจะไม่สอดคล้องกับปมหรือ unknot ใด ๆ ตัวอย่างเช่นลำดับ [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
ฉันอยากรู้เกี่ยวกับสัญกรณ์ที่คุณใช้สำหรับแผนภาพไขว้ปม มันมีชื่อหรือไม่? เป็นไปได้ไหมที่นอตที่ไม่เทียบเท่ากันสองตัวจะมีอาร์เรย์เดียวกันได้หรือไม่?
xnor

2
@ ais523: Mathematica ทั้งหมดมีKnotbuiltin! ตัวอย่างการใช้งาน: อัตราผลตอบแทน<< Units`; Convert[Knot, Mile/Hour] 1.1507794480235425 Mile/Hour(ฉันคิดว่ามันเป็นเรื่องตลกโดยไม่คำนึงว่ามันจะจริงหรือเท็จ แต่จริง ๆ แล้วมันเป็นจริง)
Greg Martin

1
[0], [0,1], [0,1,2], [1,0] และอาร์เรย์อื่น ๆ ทั้งหมดผลิต "นอต" ที่เทียบเท่ากับ unknot แต่การส่งลูปแบบง่ายจะไม่ถูกต้องใน กรณีใด ๆ เหล่านั้น สัญกรณ์ [0] โดยเฉพาะหมายถึงห่วงของเชือกที่ตัดกันอย่างแน่นอนหนึ่งครั้งและเป็นเรื่องง่ายมากที่จะบอกว่ารูปที่วาดไปที่หน้าจอเป็นไปตามสัญกรณ์อินพุตหรือไม่
J. Antonio Perez

คำตอบ:


22

โปรล็อก GNU, 622 634 668 ไบต์ในโค้ดเพจ 850

อัปเดต : บางครั้งเวอร์ชันก่อนหน้าของโปรแกรมทำให้เกิดการข้ามที่แน่นเกินไปจนไม่สามารถแสดงผลได้อย่างถูกต้องซึ่งเป็นการละเมิดข้อกำหนด ฉันได้เพิ่มในรหัสพิเศษบางอย่างเพื่อป้องกันไม่ให้

อัปเดต : เห็นได้ชัดว่ากฎ 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 สามารถกำจัดจุดจบได้เป็นครั้งคราว แต่ไม่มีข้อมูลเพียงพอที่จะปรับปรุงความซับซ้อนในการคำนวณ)

ผลลัพธ์คือผ่านเทอร์มินัลอาร์ท Prolog ของ GNU ดูเหมือนว่าต้องการชุดอักขระไบต์เดียวที่สอดคล้องกับ ASCII แต่ไม่สนใจว่าจะใช้ชุดใด (เนื่องจากใช้กับอักขระที่ตั้งค่าบิตสูงเป็นทึบแสง) ด้วยเหตุนี้ฉันจึงใช้ IBM850 ซึ่งได้รับการสนับสนุนอย่างกว้างขวางและมีอักขระวาดเส้นที่เราต้องการ

เอาท์พุต

โปรแกรมค้นหาภาพโบว์ที่เป็นไปได้ทั้งหมดตามลำดับขนาดของกล่องขอบเขตแล้วออกจากเมื่อพบภาพแรก นี่คือลักษณะของผลลัพธ์m([0]).:

 ┌┐
┌│┘
└┘ 

สิ่งนี้ใช้เวลา 290.528 วินาทีในการทำงานบนคอมพิวเตอร์ของฉัน โปรแกรมไม่ได้มีประสิทธิภาพมาก ฉันปล่อยให้มันทำงานสองชั่วโมง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 prolog, คัดลอกรหัสของคุณลงในไฟล์. txt, บันทึกเป็นไฟล์. pli แบบเข้ารหัสและในคอนโซลชื่อ m ([0])
J. Antonio Perez

มีวิธีใดบ้างที่คุณจะทำให้โปรแกรมมีประสิทธิภาพมากขึ้น?
J. Antonio Perez

ฉันสงสัยว่าโปรแกรมสามารถทำให้มีประสิทธิภาพมากขึ้น แต่ไม่มีวิธีที่ชัดเจนหรือง่าย การเปลี่ยนลำดับการประเมินเพื่อย้ายไปตามปมแทนที่จะเป็นซ้ายไปขวา / จากบนลงล่างจะช่วยได้ แต่ฉันไม่แน่ใจว่าจะช่วยได้มากแค่ไหน (และมันก็จะเป็นรหัสที่มากขึ้นด้วยเช่นกัน จึงไม่สามารถใช้งานได้ในรหัสกอล์ฟ )

คุณเข้าใจว่าทำไมฉันถึงลังเล ฉันหมายถึง ... แม้แต่รหัสที่ดีที่สุดก็ต้องมีการทดสอบและวิธีการแก้ปัญหาของคุณซับซ้อนมากจนฉันไม่สามารถตรวจสอบได้ว่ามันจะสร้างเงื่อนที่ง่ายที่สุด (ปม [2, 0, 1])
J. Antonio Perez

ฉันเข้าใจใช่ อย่างไรก็ตามนี่เป็นสิ่งที่มีอยู่ในโค้ดกอล์ฟโดยเฉพาะเมื่อพูดถึง Prolog รหัสนั้นง่ายมากซึ่งเป็นเหตุผลที่มันทำงานช้ามาก code-golfเกี่ยวกับปัญหาที่ซับซ้อนมักจะนำไปสู่การแก้ปัญหาแบบ brute-force ซึ่งจะตรวจสอบผลลัพธ์ที่เป็นไปได้ทั้งหมดตามข้อกำหนด การทำบางอย่างเพื่อให้โปรแกรมมีประสิทธิภาพมากขึ้นจะทำให้ใช้เวลานานขึ้นและอาจทำให้ยากต่อการเข้าใจและพิสูจน์ว่าถูกต้องเช่นกัน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.