Perl, 1116 1124 byte, n = 3, điểm = 1124 ^ (2/3) hoặc khoảng 108,1
Cập nhật : Bây giờ tôi đã xác minh rằng điều này hoạt động với n = 3 thông qua lực lượng vũ phu (mất vài ngày); với một chương trình phức tạp này, thật khó để kiểm tra khả năng chống bức xạ bằng tay (và tôi đã mắc một lỗi trong phiên bản trước, đó là lý do tại sao số byte tăng lên). Kết thúc cập nhật
Tôi khuyên bạn nên chuyển hướng stderr ở đâu đó mà bạn sẽ không thấy nó; chương trình này tạo ra rất nhiều cảnh báo về cú pháp đáng ngờ ngay cả khi bạn không xóa các ký tự khỏi nó.
Có thể chương trình có thể rút ngắn. Làm việc trên điều này là khá đau đớn, làm cho nó dễ dàng bỏ lỡ tối ưu hóa vi mô có thể. Tôi chủ yếu nhắm đến việc có được số lượng nhân vật có thể xóa càng nhiều càng tốt (vì đó là phần thực sự khó khăn của chương trình), và coi việc bẻ khóa mã golf là một điều gì đó tốt đẹp để nhắm đến nhưng như một điều gì đó tôi sẽ không đặt nỗ lực vô lý để tối ưu hóa (trên cơ sở rằng nó rất dễ phá vỡ sự kháng bức xạ do tai nạn).
Chương trình
Lưu ý: có ký tự Điều khiển theo nghĩa đen _
(ASCII 31) ngay trước mỗi bốn lần xuất hiện của -+
. Tôi không nghĩ rằng nó sao chép và dán chính xác vào StackOverflow, vì vậy bạn sẽ phải thêm lại nó trước khi chạy chương trình.
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
Lời giải thích
Chương trình này, khá rõ ràng, được tạo thành từ bốn chương trình nhỏ hơn giống hệt nhau được nối với nhau. Ý tưởng cơ bản là mỗi bản sao của chương trình sẽ xác minh xem nó có bị hỏng quá nặng khi chạy hay không; nếu có, nó sẽ không làm gì cả (ngoài các cảnh báo có thể phun ra) và để bản sao tiếp theo chạy; nếu nó không bị xóa (tức là không xóa hoặc ký tự bị xóa là một thứ không có gì khác biệt đối với hoạt động của chương trình), thì nó sẽ thực hiện điều đó (in ra mã nguồn của chương trình đầy đủ; đây là một câu hỏi đúng đắn, với mỗi phần có chứa một mã hóa toàn bộ mã nguồn) và thoát sau đó (ngăn chặn bất kỳ bản sao không bị hư hại khác từ in mã nguồn ra một lần nữa và do đó làm hỏng Quine bằng cách in quá nhiều văn bản).
Mỗi phần lần lượt được làm bằng hai phần độc lập về chức năng; một trình bao bọc bên ngoài và một số mã nội bộ. Như vậy, chúng ta có thể xem xét chúng một cách riêng biệt.
Bao bọc bên ngoài
Về cơ bản, trình bao bọc bên ngoài, eval<+eval<+eval< ... >####>####...>###
(cộng với một loạt dấu chấm phẩy và dòng mới có mục đích khá rõ ràng; để đảm bảo rằng các phần của chương trình sẽ được tách ra bất kể nếu một số dấu chấm phẩy, hoặc dòng mới trước chúng, sẽ bị xóa ). Điều này có thể trông khá đơn giản, nhưng nó tinh tế theo một số cách và lý do tôi chọn Perl cho thử thách này.
Trước tiên, hãy xem cách trình bao bọc hoạt động trong một bản sao không bị hư hại của chương trình. eval
phân tích cú pháp như là một hàm dựng sẵn, trong đó có một đối số. Bởi vì một cuộc tranh luận được mong đợi, +
đây là một unary +
(điều này sẽ rất quen thuộc với những người chơi golf Perl; chúng thường có ích một cách đáng ngạc nhiên). Chúng tôi vẫn đang mong đợi một đối số (chúng tôi chỉ thấy một toán tử đơn nguyên), do đó, toán tử <
tiếp theo được hiểu là bắt đầu của <>
toán tử (không có đối số tiền tố hoặc hậu tố, và do đó có thể được sử dụng ở vị trí toán hạng).
<>
là một toán tử khá kỳ lạ. Mục đích thông thường của nó là để đọc các tập tin và bạn đặt tên tập tin bên trong dấu ngoặc nhọn. Ngoài ra, nếu biểu thức không hợp lệ như một tên tệp, thì nó sẽ tạo ra toàn cầu (về cơ bản, cùng một quy trình mà các shell UNIX sử dụng để dịch văn bản được người dùng nhập vào một chuỗi các đối số dòng lệnh; thực tế đã sử dụng các phiên bản cũ hơn của Perl cái vỏ cho cái này, nhưng ngày nay Perl xử lý toàn cầu trong nội bộ). Do đó, mục đích sử dụng là dọc theo dòng <*.c>
, thường sẽ trả về một danh sách như thế nào ("foo.c", "bar.c")
. Trong ngữ cảnh vô hướng (chẳng hạn như đối sốeval
), nó chỉ trả về mục đầu tiên mà nó tìm thấy lần đầu tiên nó chạy (tương đương với đối số đầu tiên) và sẽ trả về các mục khác trong các lần chạy giả định trong tương lai không bao giờ xảy ra.
Bây giờ, shell thường xử lý các đối số dòng lệnh; nếu bạn đưa ra một cái gì đó giống như -r
không có đối số, nó sẽ được chuyển đến nguyên văn chương trình, bất kể có tệp nào có tên đó hay không. Perl hoạt động theo cùng một cách, miễn là chúng tôi đảm bảo rằng không có ký tự nào đặc biệt với vỏ hoặc Perl giữa <
khớp và khớp >
, chúng tôi có thể sử dụng hiệu quả này giống như một dạng chuỗi ký tự thực sự khó xử. Thậm chí tốt hơn, trình phân tích cú pháp của Perl cho các toán tử giống như trích dẫn có xu hướng bắt buộc khớp với các dấu ngoặc ngay cả trong các bối cảnh như thế này khi nó không có ý nghĩa, vì vậy chúng ta có thể lồng <>
một cách an toàn (đó là phát hiện cần thiết cho chương trình này). Nhược điểm chính của tất cả các lồng nhau <>
này là thoát khỏi nội dung của<>
là gần như không thể; Dường như có hai lớp không xác định với nhau <>
, vì vậy để thoát khỏi thứ gì đó ở bên trong cả ba, nó cần phải được đi trước với 63 dấu gạch chéo ngược. Tôi đã quyết định rằng mặc dù kích thước mã chỉ là vấn đề thứ yếu trong vấn đề này, nhưng gần như chắc chắn không đáng để trả loại hình phạt này cho điểm của tôi, vì vậy tôi chỉ quyết định viết phần còn lại của chương trình mà không sử dụng các ký tự vi phạm.
Vì vậy, điều gì xảy ra nếu các phần của trình bao bọc bị xóa?
- Việc xóa trong từ
eval
khiến nó biến thành một bareword , một chuỗi không có ý nghĩa. Perl không thích những thứ này, nhưng nó đối xử với họ như thể chúng được bao quanh với các trích dẫn; do đó eal<+eval<+...
được hiểu là"eal" < +eval<+...
. Điều này không ảnh hưởng gì đến hoạt động của chương trình, vì về cơ bản, nó chỉ lấy kết quả từ các evals lồng nhau (mà chúng ta không sử dụng bằng cách nào), chuyển nó thành một số nguyên và thực hiện một số so sánh vô nghĩa về nó. (Loại điều này gây ra rất nhiều thư rác cảnh báo vì rõ ràng đây không phải là điều hữu ích để thực hiện trong các trường hợp thông thường; chúng tôi chỉ sử dụng nó để xóa các xóa.) Điều này thay đổi số lượng dấu ngoặc góc mà chúng tôi cần (vì dấu ngoặc mở hiện đang được hiểu là một toán tử so sánh thay thế), nhưng chuỗi bình luận ở cuối đảm bảo chuỗi sẽ kết thúc an toàn cho dù nó được lồng bao nhiêu lần. (Có nhiều #
dấu hiệu hơn mức cần thiết nghiêm ngặt ở đây; tôi đã viết nó giống như tôi đã làm để làm cho chương trình có thể nén hơn, cho phép tôi sử dụng ít dữ liệu hơn để lưu trữ quine.)
- Nếu một
<
bị xóa, mã bây giờ phân tích như là eval(eval<...>)
. Thứ cấp, bên ngoài eval
không có tác dụng, bởi vì các chương trình mà chúng tôi đánh giá không trả lại bất kỳ thứ gì có tác dụng thực sự như một chương trình (nếu chúng trở lại bình thường, đó thường là một chuỗi null hoặc một bareword; thông thường chúng sẽ quay trở lại thông qua ngoại lệ, nguyên nhân eval
trả về chuỗi null hoặc sử dụng exit
để tránh trả về tất cả).
- Nếu
+
bị xóa, điều này không có hiệu lực ngay lập tức nếu mã liền kề còn nguyên vẹn; unary +
không có hiệu lực trên chương trình. (Lý do của các bản gốc +
là để giúp sửa chữa thiệt hại; chúng làm tăng số lượng tình huống <
được hiểu là đơn nhất <>
thay vì là toán tử quan hệ, nghĩa là bạn cần xóa nhiều hơn để tạo chương trình không hợp lệ.)
Trình bao bọc có thể bị hỏng với số lần xóa đủ, nhưng bạn cần thực hiện một loạt thao tác xóa để tạo ra thứ gì đó không phân tích cú pháp. Với bốn lần xóa, bạn có thể làm điều này:
eal<evl<eval+<...
và trong Perl, toán tử quan hệ <
là không liên kết và do đó bạn gặp lỗi cú pháp (giống như lỗi bạn gặp phải 1<2<3
). Như vậy, giới hạn cho chương trình như đã viết là n = 3. Thêm nhiều unary +
có vẻ như là một cách đầy hứa hẹn để tăng nó, nhưng vì điều đó sẽ làm cho ngày càng nhiều khả năng bên trong trình bao bọc cũng có thể bị hỏng, việc xác minh rằng phiên bản mới của chương trình hoạt động có thể rất khó khăn.
Lý do trình bao bọc rất có giá trị là vì eval
trong Perl bắt được các ngoại lệ, chẳng hạn như (ví dụ) ngoại lệ mà bạn nhận được khi bạn cố gắng biên dịch lỗi cú pháp. Bởi vì đây eval
là một chuỗi ký tự, nên quá trình biên dịch chuỗi xảy ra trong thời gian chạy và nếu nghĩa đen không biên dịch được, ngoại lệ kết quả sẽ bị bắt. Điều này gây ra eval
trả về một chuỗi null và đặt chỉ báo lỗi $@
, nhưng chúng tôi không bao giờ kiểm tra (trừ khi thỉnh thoảng thực hiện chuỗi null được trả về trong một vài phiên bản đột biến của chương trình). Điều quan trọng, điều này có nghĩa là nếu một cái gì đó sẽ xảy ra với mã bên trongtrình bao bọc, gây ra lỗi cú pháp, sau đó trình bao bọc sẽ chỉ khiến mã không làm gì thay vào đó (và chương trình sẽ tiếp tục thực thi trong nỗ lực tìm bản sao không bị hư hại của chính nó). Do đó, mã bên trong không nhất thiết phải có khả năng chống bức xạ như trình bao bọc; tất cả những gì chúng tôi quan tâm là nếu bị hỏng, nó sẽ hoạt động giống hệt với phiên bản không bị hư hại của chương trình, nếu không nó sẽ bị hỏng (cho phép eval
bắt ngoại lệ và tiếp tục) hoặc thoát bình thường mà không in bất cứ điều gì.
Bên trong bọc
Mã bên trong trình bao bọc, về cơ bản, trông như thế này (một lần nữa, có một Control - _
mà Stack Exchange sẽ không hiển thị ngay trước khi -+
):
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
Mã này được viết hoàn toàn bằng các ký tự an toàn toàn cầu và mục đích của nó là thêm một bảng chữ cái mới về dấu chấm câu để có thể viết một chương trình thực, thông qua phiên âm và đánh giá một chuỗi ký tự (chúng tôi không thể sử dụng '
hoặc "
làm trích dẫn của chúng tôi nhãn hiệu, nhưng q(
... )
cũng là một cách hợp lệ để tạo thành một chuỗi trong Perl). (Lý do cho ký tự không thể in được là vì chúng ta cần phiên âm thứ gì đó thành ký tự không gian mà không có ký tự khoảng trắng trong chương trình; do đó chúng ta tạo thành một phạm vi bắt đầu từ ASCII 31 và bắt khoảng trắng là thành phần thứ hai của phạm vi.) Rõ ràng, nếu chúng ta sản xuất một số ký tự thông qua phiên âm, chúng ta phải hy sinh các ký tự để phiên âm chúng từ, nhưng chữ in hoa không hữu ích và viết dễ dàng hơn nhiều nếu không truy cập vào các chữ cái hơn là không truy cập vào dấu chấm câu.
Đây là bảng chữ cái các dấu chấm câu có sẵn do kết quả của toàn cầu (dòng trên hiển thị mã hóa, dòng dưới ký tự mà nó mã hóa):
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $% & '() * +; <=>? @ AZz {|} ~
Đáng chú ý nhất, chúng ta có một loạt các dấu chấm câu không an toàn toàn cầu nhưng rất hữu ích trong việc viết các chương trình Perl, cùng với ký tự khoảng trắng. Tôi cũng đã lưu hai chữ cái viết hoa, nghĩa đen A
và Z
(mã hóa không phải cho chính họ, mà là T
và U
, bởi vì nó A
là cần thiết như một điểm cuối cũng như một điểm cuối phạm vi thấp hơn); điều này cho phép chúng ta tự viết hướng dẫn chuyển ngữ bằng cách sử dụng bộ ký tự được mã hóa mới (mặc dù các chữ cái viết hoa không hữu ích, nhưng chúng hữu ích trong việc chỉ định các thay đổi cho các chữ cái viết hoa). Các ký tự đáng chú ý nhất mà chúng tôi không có sẵn là [
, \
và ]
, nhưng không cần thiết (khi tôi cần một dòng mới trong đầu ra, tôi đã tạo ra nó bằng cách sử dụng dòng mới ẩnsay
thay vì cần phải viết \n
; chr 10
cũng sẽ làm việc nhưng dài dòng hơn).
Như thường lệ, chúng ta cần lo lắng về những gì sẽ xảy ra nếu bên trong của trình bao bọc bị hỏng bên ngoài chuỗi ký tự. Một hỏng eval
sẽ ngăn chặn bất cứ điều gì chạy; chúng tôi ổn với điều đó. Nếu dấu ngoặc kép bị hỏng, bên trong chuỗi không hợp lệ Perl, và do đó trình bao bọc sẽ bắt được nó (và rất nhiều phép trừ trên chuỗi có nghĩa là ngay cả khi bạn có thể làm cho Perl hợp lệ, thì nó sẽ không làm gì cả là một kết quả chấp nhận được). Làm hỏng việc chuyển ngữ, nếu đó không phải là lỗi cú pháp, sẽ xâu chuỗi được đánh giá, thường khiến nó trở thành lỗi cú pháp; Tôi không chắc chắn 100% không có trường hợp nào xảy ra sự cố này, nhưng tôi chắc chắn sẽ buộc nó vào lúc này để đảm bảo, và nó có thể dễ dàng sửa chữa nếu có.
Chương trình được mã hóa
Nhìn vào bên trong chuỗi ký tự, đảo ngược mã hóa tôi đã sử dụng và thêm khoảng trắng để dễ đọc hơn, chúng ta có được điều này (một lần nữa, hãy tưởng tượng một dấu gạch dưới điều khiển trước -+
, được mã hóa dưới dạng A
):
$o=q<
length$o ==181 || zzzz((()));
do {
chop ($t = "eval+<"x4);
$r = '=-+;-AZz-~=';
$s = '$o=q<' . $o . '>;eval$o';
eval '$s=~y' . $r . 'A-Z=';
say "$t(q($s)=~y=A-Z${r}r)" . "####>"x6;
say ";" for 1..4
} for 1..4;
exit>;
eval $o
Những người đã quen với quines sẽ nhận ra cấu trúc chung này. Phần quan trọng nhất là khi bắt đầu, nơi chúng tôi xác minh rằng $ o không bị hư hại; nếu nhân vật đã bị xóa, chiều dài của nó sẽ không phù hợp 181
, vì vậy chúng tôi chạy zzzz((()))
mà, nếu nó không phải là một lỗi cú pháp do ngoặc chưa từng có, sẽ là một lỗi thời gian chạy ngay cả khi bạn xóa bất kỳ ba nhân vật, bởi vì không ai trong số zzzz
, zzz
, zz
, và z
là một hàm và không có cách nào để ngăn chặn nó phân tích cú pháp như một hàm ngoài việc xóa (((
và gây ra lỗi cú pháp đáng ghét. Bản thân việc kiểm tra cũng miễn nhiễm với thiệt hại; các ||
thể bị hư hỏng để |
nhưng điều đó sẽ gây ra các zzzz((()))
cuộc gọi để chạy vô điều kiện; các biến hoặc hằng làm hỏng sẽ gây ra sự không khớp vì bạn đang so sánh một trong số 0
,180
, 179
, 178
Bình đẳng đối với một số tập hợp con của các chữ số của 181
; và loại bỏ một =
sẽ gây ra lỗi phân tích cú pháp và hai =
chắc chắn sẽ khiến LHS đánh giá thành số nguyên 0 hoặc chuỗi null, cả hai đều là falsey.
Cập nhật : Kiểm tra này hơi sai trong phiên bản trước của chương trình, vì vậy tôi phải chỉnh sửa nó để khắc phục sự cố. Phiên bản trước trông như thế này sau khi giải mã:
length$o==179||zzzz((()))
và có thể xóa ba dấu chấm câu đầu tiên để có được điều này:
lengtho179||zzz((()))
lengtho179
, là một bareword, là sự thật và do đó phá vỡ kiểm tra. Tôi đã sửa lỗi này bằng cách thêm hai B
ký tự (mã hóa ký tự khoảng trắng), nghĩa là phiên bản mới nhất của quine thực hiện điều này:
length$o ==181||zzzz((()))
Bây giờ không thể ẩn cả =
dấu và $
dấu mà không tạo ra lỗi cú pháp. (Tôi đã có thêm hai khoảng trống chứ không phải là một vì chiều dài 180
sẽ đặt một chữ 0
nhân vật vào nguồn, có thể bị lạm dụng trong bối cảnh này để nguyên-so sánh số không với một bareword, mà thành công.) Cập nhật End
Khi kiểm tra độ dài trôi qua, chúng tôi biết rằng bản sao không bị hư hại, ít nhất là về việc xóa ký tự khỏi nó, vì vậy tất cả chỉ đơn giản là trích dẫn từ đó (thay thế dấu chấm câu do bảng giải mã bị hỏng sẽ không bị bắt với kiểm tra này , nhưng tôi đã xác minh qua brute-buộc rằng không có ba xóa từ chỉ bảng giải mã phá vỡ Quine; có lẽ hầu hết trong số họ gây ra lỗi cú pháp). Chúng tôi có $o
trong một biến đã có, vì vậy tất cả chúng ta cần làm là hardcode giấy gói bên ngoài (với một mức độ nhỏ nén, tôi đã không bỏ ra mã golf một phần của câu hỏi hoàn toàn ). Một mẹo nhỏ là chúng tôi lưu trữ phần lớn bảng mã hóa trong$r
; chúng ta có thể in nó theo nghĩa đen để tạo phần bảng mã hóa của trình bao bọc bên trong hoặc nối một số mã xung quanh nó và eval
để chạy quá trình giải mã ngược lại (cho phép chúng ta tìm ra phiên bản được mã hóa của $ o là gì , chỉ có phiên bản được giải mã có sẵn tại thời điểm này).
Cuối cùng, nếu chúng tôi là một bản sao nguyên vẹn và do đó có thể xuất toàn bộ chương trình gốc, chúng tôi gọi exit
để ngăn các bản sao khác cũng cố gắng in chương trình ra.
Kịch bản xác minh
Không đẹp lắm, nhưng đăng nó vì có người hỏi. Tôi đã chạy nó nhiều lần với nhiều cài đặt khác nhau (thường thay đổi $min
và $max
để kiểm tra các lĩnh vực quan tâm khác nhau); đó không phải là một quá trình hoàn toàn tự động. Nó có xu hướng ngừng chạy do tải CPU nặng ở nơi khác; Khi điều này xảy ra, tôi chỉ thay đổi $min
thành giá trị đầu tiên $x
không được kiểm tra đầy đủ và tiếp tục chạy tập lệnh (do đó đảm bảo rằng tất cả các chương trình trong phạm vi cuối cùng đã được kiểm tra). Tôi chỉ kiểm tra xóa từ bản sao đầu tiên của chương trình, vì khá rõ ràng rằng việc xóa từ các bản sao khác không thể làm gì hơn.
use 5.010;
use IPC::Run qw/run/;
undef $/;
my $program = <>;
my $min = 1;
my $max = (length $program) / 4 - 3;
for my $x ($min .. $max) {
for my $y ($x .. $max) {
for my $z ($y .. $max) {
print "$x, $y, $z\n";
my $p = $program;
substr $p, $x, 1, "";
substr $p, $y, 1, "";
substr $p, $z, 1, "";
alarm 4;
run [$^X, '-M5.010'], '<', \$p, '>', \my $out, '2>', \my $err;
if ($out ne $program) {
print "Failed deleting at $x, $y, $z\n";
print "Output: {{{\n$out}}}\n";
exit;
}
}
}
}
say "All OK!";
Subleq
. Tôi nghĩ rằng nó sẽ là lý tưởng cho loại thử thách này!