Mini-Flak Quine nhanh nhất


26

Mini-Flak là một tập hợp con của Brain-Flak ngôn ngữ, nơi <>, <...>[]các hoạt động không được phép. Nói đúng ra nó không được phù hợp với regex sau:

.*(<|>|\[])

Mini-Flak là tập hợp con hoàn chỉnh Turing nhỏ nhất được biết đến của Brain-Flak.


Cách đây ít lâu tôi đã có thể tạo ra một Quine trong Mini-Flak , nhưng nó quá chậm để chạy trong vòng đời của vũ trụ.

Vì vậy, thách thức của tôi với bạn là tạo ra một Quine nhanh hơn.


Chấm điểm

Để chấm điểm mã của bạn, hãy đặt @cycờ ở cuối mã của bạn và chạy mã đó trong trình thông dịch Ruby ( Dùng thử trực tuyến sử dụng trình thông dịch ruby) bằng cách sử dụng -dcờ. Điểm của bạn sẽ được in ra STDERR như sau:

@cy <score>

Đây là số chu kỳ mà chương trình của bạn thực hiện trước khi kết thúc và giống nhau giữa các lần chạy. Vì mỗi chu kỳ mất khoảng thời gian tương tự để chạy, điểm của bạn sẽ tương quan trực tiếp với thời gian cần thiết để chạy chương trình của bạn.

Nếu Quine của bạn quá dài để bạn có thể chạy hợp lý trên máy tính của mình, bạn có thể tính toán số chu kỳ bằng tay.

Tính số chu kỳ không khó lắm. Số lượng chu kỳ tương đương với 2 lần số lượng đơn vị chạy cộng với số lượng nilad chạy. Điều này giống như thay thế mọi nilad bằng một ký tự và đếm tổng số ký tự chạy.

Ví dụ cho điểm

  • (()()()) Điểm 5 vì nó có 1 đơn nguyên và 3 nilad.

  • (()()()){({}[()])} điểm 29 vì phần đầu giống như trước và điểm 5 trong khi vòng lặp chứa 6 đơn vị và 2 nilad ghi 8. Vòng lặp được chạy 3 lần nên chúng tôi đếm số điểm của nó 3 lần. 1*5 + 3*8 = 29


Yêu cầu

Chương trình của bạn phải ...

  • Ít nhất là 2 byte

  • In mã nguồn của nó khi được thực thi trong Brain-Flak bằng -Acờ

  • Không phù hợp với regex .*(<|>|\[])


Lời khuyên

  • Trình thông dịch Crane-Flak nhanh hơn so với trình thông dịch ruby ​​nhưng thiếu một số tính năng. Tôi sẽ khuyên bạn nên kiểm tra mã của mình bằng cách sử dụng Crane-Flak trước và sau đó chấm điểm nó trong trình thông dịch ruby ​​khi bạn biết nó hoạt động. Tôi cũng khuyên bạn không nên chạy chương trình của bạn trong TIO. TIO không chỉ chậm hơn trình thông dịch trên máy tính để bàn mà còn hết thời gian chờ trong khoảng một phút. Sẽ cực kỳ ấn tượng nếu ai đó đạt được điểm thấp đủ để chạy chương trình của họ trước khi TIO hết thời gian.

  • [(...)]{}(...)[{}]hoạt động tương tự <...>nhưng không phá vỡ yêu cầu nguồn bị hạn chế

  • Bạn có thể kiểm tra Brain-FlakMini-Flak Quines nếu bạn muốn biết cách tiếp cận thử thách này.


1
"hiện tại tốt nhất" -> "chỉ hiện tại"
HyperNeutrino

Câu trả lời:


33

Mini-Flak, chu kỳ 6851113

Chương trình (theo nghĩa đen)

Tôi biết hầu hết mọi người không mong đợi một quine Mini-Flak sẽ sử dụng các ký tự không thể in được và thậm chí các ký tự nhiều byte (làm cho mã hóa có liên quan). Tuy nhiên, quine này, và không thể in được, kết hợp với kích thước của quine (93919 ký tự được mã hóa thành 102646 byte UTF-8), khiến việc đặt chương trình vào bài đăng này khá khó khăn.

Tuy nhiên, chương trình này rất lặp đi lặp lại, và như vậy, nén thực sự tốt. Vì vậy, toàn bộ chương trình có sẵn theo nghĩa đen từ Stack Exchange, có một hệ số thập phân xxdcó thể đảo ngược của gzipphiên bản được nén hoàn toàn của quine ẩn đằng sau bản thu gọn bên dưới:

(Vâng, nó lặp đi lặp lại đến mức bạn thậm chí có thể thấy các lần lặp lại sau khi nó được nén).

Câu hỏi cho biết "Tôi cũng rất khuyến nghị không chạy chương trình của bạn trong TIO. Không chỉ TIO chậm hơn trình thông dịch trên máy tính để bàn, mà còn hết thời gian trong khoảng một phút. Sẽ rất ấn tượng nếu ai đó đạt được điểm thấp để chạy chương trình của họ trước khi TIO hết thời gian. " Tôi có thể làm điều đó! Mất khoảng 20 giây để chạy trên TIO, sử dụng trình thông dịch Ruby: Dùng thử trực tuyến!

Chương trình (dễ đọc)

Bây giờ tôi đã đưa ra một phiên bản chương trình mà máy tính có thể đọc, hãy thử phiên bản mà con người có thể đọc. Tôi đã chuyển đổi các byte tạo ra quine thành codepage 437 (nếu chúng có tập bit cao) hoặc hình ảnh điều khiển Unicode (nếu chúng là mã điều khiển ASCII), thêm khoảng trắng (mọi khoảng trắng có sẵn đã được chuyển đổi thành hình ảnh kiểm soát ), được mã hóa theo chiều dài chạy bằng cú pháp «string×length»và một số bit nặng dữ liệu được tách ra:

␠
(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                («()×35» («()×44» («()×44» («()×44» («()×44» («()×45»
                … much more data encoded the same way …
                («()×117»(«()×115»(«()×117»
                «000010101011┬â┬ … many more comment characters … ┬â0┬â┬à00␈␈
                )[({})(
                    ([({})]({}{}))
                    {
                        ((()[()]))
                    }{}
                    {
                        {
                            ({}(((({}())[()])))[{}()])
                        }{}
                        (({}))
                        ((()[()]))
                    }{}
                )]{}
                %Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'×almost 241»
                ,444454545455┬ç┬ … many more comment characters … -a--┬ü␡┬ü-a␡┬ü
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

("Gần như 241" là do bản sao thứ 241 bị thiếu dấu ', nhưng khác với 240 khác.)

Giải trình

Về ý kiến

Điều đầu tiên cần giải thích là, những gì với các ký tự không thể in được và các thứ linh tinh khác không phải là lệnh Mini-Flak? Bạn có thể nghĩ rằng việc thêm ý kiến ​​vào quine chỉ khiến mọi việc trở nên khó khăn hơn, nhưng đây là một cuộc thi tốc độ (không phải là một cuộc thi quy mô), có nghĩa là các bình luận không làm tổn hại đến tốc độ của chương trình. Trong khi đó, Brain-Flak, và do đó là Mini-Flak, chỉ cần đổ nội dung của ngăn xếp vào đầu ra tiêu chuẩn; nếu bạn phải đảm bảo rằng ngăn xếp chỉ chứacác ký tự tạo nên các lệnh của chương trình của bạn, bạn sẽ phải dành chu kỳ để dọn dẹp ngăn xếp. Như vậy, Brain-Flak bỏ qua hầu hết các ký tự, miễn là chúng tôi đảm bảo rằng các thành phần ngăn xếp rác không hợp lệ với các lệnh Brain-Flak (biến nó thành một polyglot Brain-Flak / Mini-Flak) và không âm tính hoặc bên ngoài phạm vi Unicode, chúng ta có thể để chúng trên ngăn xếp, cho phép chúng xuất ra và đặt cùng một ký tự trong chương trình của chúng ta tại cùng một vị trí để giữ lại thuộc tính quine.

Có một cách đặc biệt quan trọng chúng ta có thể tận dụng điều này. Quine hoạt động bằng cách sử dụng một chuỗi dữ liệu dài và về cơ bản, tất cả đầu ra từ quine được tạo ra bằng cách định dạng chuỗi dữ liệu theo nhiều cách khác nhau. Chỉ có một chuỗi dữ liệu, mặc dù thực tế là chương trình có nhiều phần; vì vậy chúng ta cần có khả năng sử dụng cùng một chuỗi dữ liệu để in các phần khác nhau của chương trình. Thủ thuật "dữ liệu rác không quan trọng" cho phép chúng tôi thực hiện việc này một cách rất đơn giản; chúng tôi lưu trữ các ký tự tạo nên chương trình trong chuỗi dữ liệu bằng cách thêm hoặc trừ một giá trị vào hoặc từ mã ASCII của chúng. Cụ thể, các ký tự tạo nên bắt đầu chương trình được lưu trữ dưới dạng mã ASCII + 4 của chúng, các ký tự tạo thành phần được lặp lại gần 241 lần dưới dạng mã ASCII của chúng - 4,mỗi ký tự của chuỗi dữ liệu có phần bù; ví dụ, nếu chúng ta in nó với 4 được thêm vào mỗi mã ký tự, chúng ta sẽ nhận được một lần lặp lại của phần lặp lại, với một số nhận xét trước và sau. (Những nhận xét đó chỉ đơn giản là các phần khác của chương trình, với các mã ký tự được dịch chuyển để chúng không tạo thành bất kỳ lệnh Brain-Flak hợp lệ nào, bởi vì phần bù sai đã được thêm vào. Chúng ta phải tránh các lệnh Brain-Flak, không chỉ Mini- Các lệnh Flak, để tránh vi phạm phần của câu hỏi, sự lựa chọn bù đắp được thiết kế để đảm bảo điều này.)

Do thủ thuật nhận xét này, chúng tôi thực sự chỉ cần có thể xuất chuỗi dữ liệu được định dạng theo hai cách khác nhau: a) được mã hóa theo cùng một cách như trong nguồn, b) dưới dạng mã ký tự với phần bù được chỉ định được thêm vào mỗi mã. Đó là một sự đơn giản hóa lớn làm cho chiều dài thêm vào hoàn toàn xứng đáng.

Cấu trúc chương trình

Chương trình này bao gồm bốn phần: phần giới thiệu, chuỗi dữ liệu, bộ định dạng chuỗi dữ liệu và phần ngoài. Giới thiệu và hướng ngoại về cơ bản chịu trách nhiệm chạy chuỗi dữ liệu và bộ định dạng của nó trong một vòng lặp, chỉ định định dạng phù hợp mỗi lần (nghĩa là mã hóa hay bù và sử dụng phần bù nào). Chuỗi dữ liệu chỉ là dữ liệu và là phần duy nhất của quine mà các ký tự tạo nên nó không được chỉ định theo nghĩa đen trong chuỗi dữ liệu (việc đó rõ ràng là không thể, vì nó sẽ phải dài hơn chính nó); do đó nó được viết theo cách đặc biệt dễ dàng để tái sinh từ chính nó. Trình định dạng chuỗi dữ liệu được tạo thành từ 241 phần gần như giống hệt nhau, mỗi phần định dạng một mốc dữ liệu cụ thể trong số 241 trong chuỗi dữ liệu.

Mỗi phần của chương trình có thể được tạo ra thông qua chuỗi dữ liệu và định dạng của nó như sau:

  • Để tạo ra outro, định dạng chuỗi dữ liệu với offset +8
  • Để tạo bộ định dạng chuỗi dữ liệu, định dạng chuỗi dữ liệu với offset +4, 241 lần
  • Để tạo chuỗi dữ liệu, định dạng chuỗi dữ liệu thông qua mã hóa thành định dạng nguồn
  • Để tạo phần giới thiệu, định dạng chuỗi dữ liệu với offset -4

Vì vậy, tất cả những gì chúng ta phải làm là xem xét cách thức các phần này của chương trình hoạt động.

Chuỗi dữ liệu

(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45» …

Chúng ta cần một mã hóa đơn giản cho chuỗi dữ liệu vì chúng ta phải có khả năng đảo ngược mã hóa trong mã Mini-Flak. Bạn không thể đơn giản hơn thế này nhiều!

Ý tưởng chính đằng sau câu hỏi này (ngoài thủ thuật nhận xét) là lưu ý rằng về cơ bản chỉ có một nơi chúng ta có thể lưu trữ một lượng lớn dữ liệu: "tổng các giá trị trả về lệnh" trong các mức lồng nhau khác nhau của nguồn chương trình. (Điều này thường được gọi là ngăn xếp thứ ba, mặc dù Mini-Flak không có ngăn xếp thứ hai, vì vậy "ngăn xếp hoạt động" có thể là một tên tốt hơn trong bối cảnh Mini-Flak.) Các khả năng khác để lưu trữ dữ liệu sẽ là ngăn xếp chính / đầu tiên (không hoạt động bởi vì đó là nơi đầu ra của chúng ta phải đi và chúng ta không thể di chuyển đầu ra qua bộ lưu trữ theo cách hiệu quả từ xa) và được mã hóa thành một khối trong một phần tử ngăn xếp duy nhất (không phù hợp với vấn đề này vì nó mất thời gian theo cấp số nhân trích xuất dữ liệu từ nó); khi bạn loại bỏ chúng, ngăn xếp làm việc là vị trí duy nhất còn lại.

Để "lưu trữ" dữ liệu trên ngăn xếp này, chúng tôi sử dụng các lệnh không cân bằng (trong trường hợp này là nửa đầu của (…)lệnh), sẽ được cân bằng trong bộ định dạng chuỗi dữ liệu sau này. Mỗi lần chúng ta đóng một trong các lệnh này trong bộ định dạng, nó sẽ đẩy tổng số dữ liệu được lấy từ chuỗi dữ liệu và các giá trị trả về của tất cả các lệnh ở mức lồng nhau trong bộ định dạng; chúng ta có thể đảm bảo rằng cái sau thêm vào 0, vì vậy trình định dạng chỉ đơn giản nhìn thấy các giá trị đơn lẻ được lấy từ chuỗi dữ liệu.

Định dạng rất đơn giản : (, theo sau là n bản sao của (), trong đó n là số chúng tôi muốn lưu trữ. (Lưu ý rằng điều này có nghĩa là chúng tôi chỉ có thể lưu trữ các số không âm và phần tử cuối cùng của chuỗi dữ liệu cần phải dương.)

Một điểm hơi không trực quan về chuỗi dữ liệu là thứ tự của nó. "Bắt đầu" của chuỗi dữ liệu là kết thúc gần hơn khi bắt đầu chương trình, tức là mức lồng nhau ngoài cùng; phần này được định dạng cuối cùng (vì trình định dạng chạy từ mức trong cùng đến mức lồng ngoài cùng). Tuy nhiên, mặc dù được định dạng lần cuối, nó sẽ được in trước, bởi vì các giá trị được đẩy lên ngăn xếp trước được in bởi trình thông dịch Mini-Flak. Nguyên tắc tương tự áp dụng cho toàn bộ chương trình; chúng ta cần định dạng outro trước, sau đó là định dạng chuỗi dữ liệu, sau đó là chuỗi dữ liệu, sau đó là intro, tức là đảo ngược thứ tự mà chúng được lưu trữ trong chương trình.

Trình định dạng chuỗi dữ liệu

)[({})(
    ([({})]({}{}))
    {
        ((()[()]))
    }{}
    {
        {
            ({}(((({}())[()])))[{}()])
        }{}
        (({}))
        ((()[()]))
    }{}
)]{}

Trình định dạng chuỗi dữ liệu được tạo thành từ 241 phần, mỗi phần có mã giống nhau (một phần có một nhận xét khác nhau), mỗi phần định dạng một ký tự cụ thể của chuỗi dữ liệu. (Chúng ta không thể sử dụng một vòng lặp ở đây: chúng ta cần một chuỗi không cân bằng )để đọc chuỗi dữ liệu thông qua khớp với chuỗi không cân bằng (và chúng ta không thể đặt một trong số chúng vào trong một{…} vòng lặp, dạng vòng lặp duy nhất tồn tại. Thay vào đó, chúng ta " hủy đăng ký "bộ định dạng và chỉ cần lấy phần giới thiệu / đầu ra để xuất chuỗi dữ liệu với phần bù của bộ định dạng 241 lần.)

)[({})( … )]{}

Phần ngoài cùng của một phần tử định dạng đọc một phần tử của chuỗi dữ liệu; sự đơn giản của mã hóa chuỗi dữ liệu dẫn đến một chút phức tạp trong việc đọc nó. Chúng tôi bắt đầu bằng cách đóng giá trị chưa từng có (…)trong chuỗi dữ liệu, sau đó phủ định ( […]) hai giá trị: mốc thời gian chúng tôi vừa đọc từ chuỗi dữ liệu ( ({})) và giá trị trả về của phần còn lại của chương trình. Chúng tôi sao chép giá trị trả về của phần còn lại của phần tử định dạng (…)và thêm bản sao vào phiên bản phủ định với {}. Kết quả cuối cùng là giá trị trả về của phần tử chuỗi dữ liệu và phần tử định dạng cùng nhau là mốc chuẩn trừ đi phần tử trừ đi giá trị trả về cộng với giá trị trả về hoặc 0; điều này là cần thiết để làm cho phần tử chuỗi dữ liệu tiếp theo tạo ra giá trị chính xác.

([({})]({}{}))

Trình định dạng sử dụng phần tử ngăn xếp trên cùng để biết nó ở chế độ nào (0 = định dạng trong định dạng chuỗi dữ liệu, bất kỳ giá trị nào khác = phần bù cho đầu ra với). Tuy nhiên, chỉ cần đọc chuỗi dữ liệu, mốc chuẩn nằm trên cùng của định dạng trên ngăn xếp và chúng tôi muốn chúng theo cách khác. Mã này là một biến thể ngắn hơn của mã hoán đổi Brain-Flak, lấy một trên b để b trên một  +  b ; Nó không chỉ ngắn hơn mà còn (trong trường hợp cụ thể này) hữu ích hơn, bởi vì tác dụng phụ của việc thêm b vào a không có vấn đề gì khi b bằng 0 và khi b không bằng 0, nó thực hiện phép tính bù cho chúng tôi.

{
    ((()[()]))
}{}
{
    …
    ((()[()]))
}{}

Brain-Flak chỉ có một cấu trúc luồng điều khiển, vì vậy nếu chúng ta muốn bất cứ thứ gì ngoài whilevòng lặp, nó sẽ mất một chút công việc. Đây là một cấu trúc "phủ định"; nếu có 0 trên đỉnh của ngăn xếp, nó sẽ loại bỏ nó, nếu không, nó đặt 0 trên đỉnh của ngăn xếp. (Nó hoạt động khá đơn giản: miễn là không có 0 trên đỉnh của ngăn xếp, đẩy 1 - 1 lên ngăn xếp hai lần; khi bạn hoàn thành, hãy bật phần tử ngăn xếp trên cùng.)

Có thể đặt mã bên trong một cấu trúc phủ định, như được thấy ở đây. Mã sẽ chỉ chạy nếu đỉnh của ngăn xếp là khác không; vì vậy nếu chúng ta có hai cấu trúc phủ định, giả sử hai phần tử ngăn xếp trên cùng không cùng không bằng 0, chúng sẽ triệt tiêu lẫn nhau, nhưng bất kỳ mã nào trong cấu trúc đầu tiên sẽ chỉ chạy nếu phần tử ngăn xếp trên cùng là khác không và mã bên trong cấu trúc thứ hai sẽ chỉ chạy nếu phần tử ngăn xếp trên cùng bằng không. Nói cách khác, đây là tương đương với một câu lệnh if-then-other.

Trong mệnh đề "then", chạy nếu định dạng khác không, chúng ta thực sự không có gì để làm; những gì chúng tôi muốn là đẩy dữ liệu + offset vào ngăn xếp chính (để nó có thể là đầu ra ở cuối chương trình), nhưng nó đã có sẵn. Vì vậy, chúng ta chỉ phải đối phó với trường hợp mã hóa thành phần chuỗi dữ liệu ở dạng nguồn.

{
    ({}(((({}())[()])))[{}()])
}{}
(({}))

Đây là cách chúng tôi làm điều đó. Các {({}( … )[{}()])}{}cấu trúc nên quen thuộc như một vòng lặp với một số cụ thể của sự lặp lại (mà hoạt động bằng cách di chuyển các vòng lặp ngược lại với chồng làm việc và giữ nó ở đó, nó sẽ được an toàn từ bất kỳ mã khác, bởi vì truy cập vào các ngăn xếp làm việc được gắn với mức độ lồng của chương trình). Phần thân của vòng lặp là ((({}())[()])), tạo ra ba bản sao của phần tử ngăn xếp trên cùng và thêm 1 vào mức thấp nhất. Nói cách khác, nó biến đổi một số 40 trên đỉnh của ngăn xếp thành 40 trên 40 trên 41 hoặc được xem là ASCII, (thành ((); chạy này nhiều lần sẽ làm cho (thành (()vào (()()vào (()()()và như vậy, và do đó là một cách đơn giản để tạo ra chuỗi dữ liệu của chúng tôi (giả định rằng có một (trên đỉnh của ngăn xếp đã được).

Khi chúng ta đã thực hiện xong vòng lặp, (({}))sao chép đỉnh của ngăn xếp (để bây giờ nó bắt đầu ((()…thay vì (()…. Phần đầu (sẽ được sử dụng bởi bản sao tiếp theo của trình định dạng chuỗi dữ liệu để định dạng ký tự tiếp theo (nó sẽ mở rộng nó thành (()(()…sau đó (()()(()…, v.v., do đó, điều này tạo ra sự phân tách (trong chuỗi dữ liệu).

%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'

Có một chút quan tâm cuối cùng trong bộ định dạng chuỗi dữ liệu. OK, vì vậy, hầu hết đây chỉ là 4 điểm mã hướng ra ngoài; tuy nhiên, dấu nháy đơn đó ở cuối có thể không phù hợp. '(codepoint 39) sẽ chuyển sang +(codepoint 43), đây không phải là lệnh Brain-Flak, vì vậy bạn có thể đoán rằng nó ở đó cho một số mục đích khác.

Lý do là ở đây là vì trình định dạng chuỗi dữ liệu dự kiến ​​đã có sẵn (trên ngăn xếp (nó không chứa 40 chữ ở bất cứ đâu). Các'thực ra là ở đầu khối được lặp lại để tạo thành bộ định dạng chuỗi dữ liệu, không phải là kết thúc, vì vậy sau khi các ký tự của bộ định dạng chuỗi dữ liệu đã được đẩy lên ngăn xếp (và mã sắp chuyển sang in chuỗi dữ liệu chính nó), outro điều chỉnh 39 trên đỉnh của ngăn xếp thành 40, sẵn sàng cho bộ định dạng (chính bộ định dạng đang chạy lần này, không phải là đại diện của nó trong nguồn) để sử dụng nó. Đó là lý do tại sao chúng tôi có "gần 241" bản sao của bộ định dạng; bản sao đầu tiên bị thiếu ký tự đầu tiên. Và ký tự đó, dấu nháy đơn, là một trong ba ký tự trong chuỗi dữ liệu không tương ứng với mã Mini-Flak ở đâu đó trong chương trình; nó hoàn toàn là một phương pháp cung cấp một hằng số.

Giới thiệu và hướng ngoại

(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                …
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

Giới thiệu và hướng ngoại về mặt khái niệm là cùng một phần của chương trình; lý do duy nhất chúng ta rút ra một điểm khác biệt là outro cần được xuất ra trước chuỗi dữ liệu và định dạng của nó (để nó in ra sau chúng), trong khi phần giới thiệu cần được xuất sau chúng (in trước chúng).

(((()()()()){}))

Chúng tôi bắt đầu bằng cách đặt hai bản sao 8 trên ngăn xếp. Đây là phần bù cho lần lặp đầu tiên. Bản sao thứ hai là bởi vì vòng lặp chính dự kiến ​​sẽ có một phần tử rác ở trên cùng của ngăn xếp phía trên phần bù, bị bỏ lại phía sau bài kiểm tra quyết định có tồn tại vòng lặp chính hay không, và vì vậy chúng ta cần đặt một phần tử rác ở đó để nó không vứt bỏ yếu tố chúng ta thực sự muốn; một bản sao là cách nhanh nhất (do đó nhanh nhất để xuất) để làm điều đó.

Có những đại diện khác của số 8 không dài hơn số này. Tuy nhiên, khi đi mã nhanh nhất, đây chắc chắn là lựa chọn tốt nhất. Đối với một điều, sử dụng ()()()()là nhanh hơn, nói, (()()){}bởi vì mặc dù cả hai đều dài 8 ký tự, trước đây là một chu kỳ nhanh hơn, bởi vì (…)được tính là 2 chu kỳ, nhưng ()chỉ là một. Tuy nhiên, việc lưu một chu kỳ là không đáng kể so với việc xem xét lớn hơn nhiều đối với một : ()có các điểm mã thấp hơn nhiều so với {}do đó, việc tạo ra đoạn dữ liệu cho chúng sẽ nhanh hơn nhiều (và đoạn dữ liệu sẽ chiếm ít không gian hơn trong mã, quá).

{{} … }{}{}

Vòng lặp chính. Điều này không tính các lần lặp (đó là một whilevòng lặp, không phải là một forvòng lặp và sử dụng một bài kiểm tra để thoát ra). Khi nó thoát, chúng tôi loại bỏ hai phần tử ngăn xếp trên cùng; phần tử trên cùng là 0 vô hại, nhưng phần tử bên dưới sẽ là "định dạng để sử dụng cho lần lặp tiếp theo", phần này (là phần bù âm) là một số âm và nếu có bất kỳ số âm nào trong ngăn xếp khi Mini Chương trình -Flak thoát, trình thông dịch gặp sự cố khi cố gắng xuất chúng.

Vì vòng lặp này sử dụng một thử nghiệm rõ ràng để thoát ra, kết quả của thử nghiệm đó sẽ được để lại trên ngăn xếp, vì vậy chúng tôi loại bỏ nó như là điều đầu tiên chúng tôi làm (giá trị của nó không hữu ích).

(({})[(()()()())])

Mã này đẩy 4 và f  - 4 lên trên một phần tử stack f , trong khi giữ nguyên phần tử đó. Chúng tôi đang tính toán định dạng cho lần lặp tiếp theo trước (trong khi chúng tôi có 4 liên tục tiện dụng) và đồng thời sắp xếp ngăn xếp theo đúng thứ tự cho một số phần tiếp theo của chương trình: chúng tôi sẽ sử dụng f làm định dạng cho lần lặp này và 4 là cần thiết trước đó.

(({})( … )[{}])

Điều này lưu một bản sao f  - 4 trên ngăn xếp làm việc, để chúng ta có thể sử dụng nó cho lần lặp tiếp theo. (Giá trị của f sẽ vẫn có mặt tại thời điểm đó, nhưng nó sẽ được ở một nơi vụng về trên stack, và thậm chí nếu chúng ta có thể cơ động nó vào địa điểm chính xác, chúng tôi phải bỏ ra chu kỳ trừ đi 4 từ nó, và chu trình in mã để thực hiện phép trừ đó. Đơn giản hơn rất nhiều để lưu trữ mã ngay bây giờ.)

{{}{}((()[()]))}{}

Một thử nghiệm để xem nếu độ lệch là 4 (tức là f  - 4 là 0). Nếu đúng như vậy, chúng tôi đang in bộ định dạng chuỗi dữ liệu, vì vậy chúng tôi cần chạy chuỗi dữ liệu và bộ định dạng của nó 240 lần thay vì chỉ một lần ở phần bù này. Mã này khá đơn giản: nếu f  - 4 không khác, hãy thay thế f  - 4 và 4 bằng một cặp số không; sau đó trong cả hai trường hợp, bật phần tử ngăn xếp trên cùng. Bây giờ chúng ta có một số trên f trên ngăn xếp, là 4 (nếu chúng ta muốn in lần lặp này tới 241 lần) hoặc 0 (nếu chúng ta chỉ muốn in nó một lần).

(
    ((((((({})){}){}{})){}{}){}){}
    ()
)

Đây là một loại thú vị của hằng số Brain-Flak / Mini-Flak; dòng dài ở đây đại diện cho số 60. Bạn có thể bị nhầm lẫn ở chỗ thiếu (), thường là ở khắp mọi nơi trong các hằng số Brain-Flak; đây không phải là một số thông thường, mà là một số của Giáo hội, diễn giải các số như là một phép toán trùng lặp. Ví dụ, chữ số Church cho 60, được thấy ở đây, tạo ra 60 bản sao đầu vào của nó và kết hợp tất cả chúng lại với nhau thành một giá trị duy nhất; trong Brain-Flak, những thứ duy nhất chúng ta có thể kết hợp là những con số thông thường, ngoài ra, vì vậy chúng ta cuối cùng đã thêm 60 bản sao của đỉnh ngăn xếp và do đó nhân số đỉnh của ngăn xếp với 60.

Là một lưu ý phụ, bạn có thể sử dụng công cụ tìm số Underload , tạo ra các số Church trong cú pháp Underload, để tìm số thích hợp trong Mini-Flak. Các chữ số dưới tải (khác 0) sử dụng các thao tác "phần tử ngăn xếp trên cùng trùng lặp" :và "kết hợp hai phần tử ngăn xếp trên cùng" *; cả hai thao tác này đều tồn tại trong Brain-Flak, vì vậy bạn chỉ cần dịch :sang ), *nạp {}trước {}và thêm (vào lúc bắt đầu để cân bằng (điều này là sử dụng hỗn hợp kỳ lạ của ngăn xếp chính và ngăn xếp hoạt động, nhưng nó hoạt động).

Đoạn mã cụ thể này sử dụng số 60 của nhà thờ (có hiệu quả là một đoạn "nhân với 60"), cùng với số gia, để tạo biểu thức 60 x  + 1. Vì vậy, nếu chúng ta có 4 từ bước trước, điều này mang lại cho chúng ta một giá trị là 241 hoặc nếu chúng ta có 0, chúng ta chỉ nhận được giá trị là 1, tức là điều này sẽ tính toán chính xác số lần lặp mà chúng ta cần.

Sự lựa chọn của 241 không phải là ngẫu nhiên; nó là một giá trị được chọn là a) xấp xỉ độ dài mà chương trình sẽ kết thúc bằng mọi cách và b) 1 hơn 4 lần một số tròn. Số tròn, 60 trong trường hợp này, có xu hướng có các biểu diễn ngắn hơn là số của Giáo hội vì bạn có sự linh hoạt hơn trong các yếu tố để sao chép. Chương trình có chứa phần đệm sau này để mang lại độ dài chính xác lên tới 241.

{
    ({}(
        …
    )[{}()])
}{}

Đây là một vòng lặp for, giống như vòng lặp đã thấy trước đó, chỉ đơn giản là chạy mã bên trong nó một số lần bằng với đỉnh của ngăn xếp chính (mà nó tiêu thụ; chính bộ đếm vòng lặp được lưu trữ trên ngăn xếp làm việc, nhưng khả năng hiển thị của được gắn với mức lồng nhau của chương trình và do đó không thể có bất cứ thứ gì ngoại trừ chính vòng lặp for tương tác với nó). Điều này thực sự chạy chuỗi dữ liệu và trình định dạng của nó 1 hoặc 241 lần và vì hiện tại chúng tôi đã bật tất cả các giá trị mà chúng tôi đang sử dụng để tính toán luồng điều khiển từ ngăn xếp chính, chúng tôi có định dạng để sử dụng trên đầu trang, sẵn sàng cho các định dạng để sử dụng.

(␀␀!S␠su! … many more comment characters … oq␝qoqoq)

Nhận xét ở đây không hoàn toàn không có hứng thú. Đối với một điều, có một vài lệnh Brain-Flak; phần )cuối được tạo ra một cách tự nhiên như là một tác dụng phụ của cách chuyển đổi giữa các phân đoạn khác nhau của chương trình, do đó, (khi bắt đầu được thêm thủ công để cân bằng nó (và mặc dù độ dài của nhận xét bên trong, đặt một nhận xét bên trong một ()lệnh vẫn là một ()lệnh, vì vậy tất cả những gì nó làm là thêm 1 vào giá trị trả về của chuỗi dữ liệu và định dạng của nó, một cái gì đó mà vòng lặp for hoàn toàn bỏ qua).

Đáng chú ý hơn, những ký tự NUL khi bắt đầu bình luận rõ ràng không bù đắp cho bất cứ điều gì (ngay cả sự khác biệt giữa +8 và -4 cũng không đủ để biến (NUL thành NUL). Đó là những phần đệm thuần túy để đưa chuỗi dữ liệu 239 phần tử lên tới 241 phần tử (dễ dàng tự trả tiền: sẽ mất nhiều hơn hai byte để tạo 1 so với 239 thay vì 1 so với 241 khi tính toán số lần lặp cần thiết ). NUL được sử dụng làm ký tự đệm bởi vì nó có mật mã thấp nhất có thể (làm cho mã nguồn cho chuỗi dữ liệu ngắn hơn và do đó nhanh hơn để xuất ra).

{}({}())

Bỏ phần tử ngăn xếp trên cùng (định dạng chúng tôi đang sử dụng), thêm 1 vào phần tiếp theo (ký tự cuối cùng được xuất, tức là ký tự đầu tiên được in, của phần chương trình chúng tôi vừa định dạng). Chúng tôi không cần định dạng cũ nữa (định dạng mới đang ẩn trên ngăn xếp hoạt động); và phần tăng là vô hại trong hầu hết các trường hợp và thay đổi 'ở một đầu của biểu diễn nguồn của bộ định dạng chuỗi dữ liệu thành một ((yêu cầu trên ngăn xếp cho lần tiếp theo chúng ta chạy bộ định dạng, để định dạng chính chuỗi dữ liệu). Chúng ta cần một phép biến đổi như thế trong phần ngoài hoặc phần giới thiệu, bởi vì việc bắt buộc mỗi phần tử định dạng chuỗi dữ liệu bắt đầu (sẽ làm cho nó phức tạp hơn một chút (vì chúng ta cần phải đóng (và sau đó hoàn tác lại hiệu ứng của nó) bằng cách nào đó chúng ta cần phải tạo thêm (ở đâu đó bởi vì chúng ta chỉ có gần 241 bản sao của bộ định dạng, không phải tất cả là 241 (vì vậy tốt nhất là một nhân vật vô hại như 'là một nhân vật bị thiếu).

(({})(()()()()){})

Cuối cùng, kiểm tra thoát vòng lặp. Đỉnh hiện tại của ngăn xếp chính là định dạng chúng ta cần cho lần lặp tiếp theo (vừa quay trở lại ngăn xếp hoạt động). Điều này sao chép nó và thêm 8 vào bản sao; giá trị kết quả sẽ bị loại bỏ trong lần tiếp theo vòng lặp. Tuy nhiên, nếu chúng ta chỉ in phần giới thiệu, phần bù là -4 nên phần bù cho "lần lặp tiếp theo" sẽ là -8; -8 + 8 là 0, do đó vòng lặp sẽ thoát thay vì tiếp tục lặp lại sau đó.


16

128,673,515 chu kỳ

Dùng thử trực tuyến

Giải trình

Lý do khiến các câu đố của miniflak bị chậm lại là do sự thiếu truy cập ngẫu nhiên của miniflak. Để giải quyết vấn đề này, tôi tạo một khối mã lấy một số và trả về một mốc thời gian. Mỗi mốc thời gian đại diện cho một ký tự như trước đây và mã chính chỉ đơn giản truy vấn khối này cho từng ký tự một. Điều này về cơ bản hoạt động như một khối bộ nhớ truy cập ngẫu nhiên.


Khối mã này có hai yêu cầu.

  • Nó phải lấy một số và chỉ xuất ra mã ký tự cho ký tự đó

  • Phải dễ dàng tái tạo bảng tra cứu từng chút một trong Brain-Flak

Để xây dựng khối này, tôi thực sự đã sử dụng lại một phương thức từ bằng chứng của mình rằng miniflak đã hoàn thành Turing. Đối với mỗi mốc thời gian, có một khối mã trông như thế này:

(({}[()])[(())]()){(([({}{})]{}))}{}{(([({}{}(%s))]{}))}{}

Điều này trừ đi một số từ số trên cùng của ngăn xếp và nếu không thì đẩy %smốc bên dưới nó. Vì mỗi phần giảm kích thước theo một nếu bạn bắt đầu bằng n trên ngăn xếp, bạn sẽ lấy lại mốc thứ n.

Điều này là tốt đẹp và mô-đun, vì vậy nó có thể được viết bởi một chương trình một cách dễ dàng.


Tiếp theo chúng ta phải thiết lập máy thực sự dịch bộ nhớ này vào nguồn. Điều này bao gồm 3 phần như vậy:

(([()]())())
{({}[(
  -Look up table-
 )]{})
 1. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}(([{}]))(()()()()()))]{})}{}

 2. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}
      (({}[(
      ({}[()(((((()()()()()){}){}){}))]{}){({}[()(({}()))]{}){({}[()(({}((((()()()){}){}){}()){}))]{}){({}[()(({}()()))]{}){({}[()(({}(((()()()()())){}{}){}))]{}){([(({}{}()))]{})}}}}}{}
      (({}({}))[({}[{}])])
     )]{}({})[()]))
      ({[()]([({}({}[({})]))]{})}{}()()()()()[(({}({})))]{})
    )]{})}{}

 3. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}
     (({}(({}({}))[({}[{}])][(
     ({}[()(
      ([()](((()()[(((((((()()()){})())){}{}){}){})]((((()()()()())){}{}){})([{}]([()()](({})(([{}](()()([()()](((((({}){}){}())){}){}{}))))))))))))
     )]{})
     {({}[()(((({})())[()]))]{})}{}
     (([(((((()()()()){}){}()))){}{}([({})]((({})){}{}))]()()([()()]({}(({})([()]([({}())](({})([({}[()])]()(({})(([()](([({}()())]()({}([()](([((((((()()()())()){}){}){}()){})]({}()(([(((((({})){}){}())){}{})]({}([((((({}())){}){}){}()){}()](([()()])(()()({}(((((({}())())){}{}){}){}([((((({}))){}()){}){}]([((({}[()])){}{}){}]([()()](((((({}())){}{}){}){})(([{}](()()([()()](()()(((((()()()()()){}){}){}()){}()(([((((((()()()())){}){}())){}{})]({}([((((({})()){}){}){}()){}()](([()()])(()()({}(((((({}){}){}())){}){}{}(({})))))))))))))))))))))))))))))))))))))))))))))))
     )]{})[()]))({()()()([({})]{})}{}())
    )]{})}{}

   ({}[()])
}{}{}{}
(([(((((()()()()){}){}())){}{})]((({}))([()]([({}())]({}()([()]((()([()]((()([({})((((()()()()){}){}()){})]()())([({})]({}([()()]({}({}((((()()()()()){}){}){}))))))))))))))))))

Máy bao gồm bốn phần được chạy theo thứ tự bắt đầu bằng 1 và kết thúc bằng 3. Tôi đã dán nhãn chúng vào đoạn mã trên. Mỗi phần cũng sử dụng định dạng bảng tra cứu tương tự mà tôi sử dụng để mã hóa. Điều này là do toàn bộ chương trình được chứa trong một vòng lặp và chúng tôi không muốn chạy mọi phần mỗi khi chúng tôi chạy qua vòng lặp nên chúng tôi đặt cùng một cấu trúc RA và truy vấn phần chúng tôi mong muốn mỗi lần.

1

Phần 1 là phần thiết lập đơn giản.

Chương trình cho các truy vấn đầu tiên phần 1 và mốc 0. Dữ liệu 0 không tồn tại vì vậy thay vì trả về giá trị đó, nó chỉ đơn giản là giảm truy vấn một lần cho mỗi mốc. Điều này rất hữu ích vì chúng ta có thể sử dụng kết quả để xác định số lượng dữ liệu, sẽ trở nên quan trọng trong các phần sau. Phần 1 ghi lại số lượng dữ liệu bằng cách phủ định kết quả và truy vấn Phần 2 và mốc thời gian cuối cùng. Vấn đề duy nhất là chúng ta không thể truy vấn trực tiếp phần 2. Vì còn một phần giảm nữa, chúng ta cần truy vấn phần không tồn tại 5. Thực tế đây sẽ là trường hợp mỗi khi chúng ta truy vấn một phần trong phần khác. Tôi sẽ bỏ qua điều này trong phần giải thích của mình, tuy nhiên nếu bạn đang tìm mã chỉ cần nhớ 5 nghĩa là quay lại một phần và 4 nghĩa là chạy lại phần đó.

2

Phần 2 giải mã dữ liệu thành các ký tự tạo nên mã sau khối dữ liệu. Mỗi lần nó dự kiến ​​stack sẽ xuất hiện như vậy:

Previous query
Result of query
Number of data
Junk we shouldn't touch...

Nó ánh xạ từng kết quả có thể (một số từ 1 đến 6) đến một trong sáu ký tự miniflak hợp lệ ( (){}[]) và đặt nó bên dưới số lượng dữ liệu với "Rác chúng ta không nên chạm vào". Điều này mang lại cho chúng ta một ngăn xếp như:

Previous query
Number of data
Junk we shouldn't touch...

Từ đây, chúng ta cần truy vấn mốc thời gian tiếp theo hoặc nếu chúng ta đã truy vấn tất cả chúng chuyển sang phần 3. Truy vấn trước đó không thực sự là truy vấn chính xác được gửi đi mà là truy vấn trừ đi số lượng dữ liệu trong khối. Điều này là do mỗi mốc thời gian làm giảm truy vấn của một truy vấn nên truy vấn xuất hiện khá sai lệch. Để tạo truy vấn tiếp theo, chúng tôi thêm một bản sao của số lượng dữ liệu và trừ đi một dữ liệu. Bây giờ ngăn xếp của chúng tôi trông như:

Next query
Number of data
Junk we shouldn't touch...

Nếu truy vấn tiếp theo của chúng tôi bằng 0, chúng tôi đã đọc tất cả bộ nhớ cần thiết trong phần 3, vì vậy chúng tôi sẽ thêm số lượng dữ liệu vào truy vấn và tát 4 trên đầu ngăn xếp để chuyển sang phần 3. Nếu truy vấn tiếp theo không phải là 0, chúng tôi đặt 5 trên ngăn xếp để chạy lại phần 2.

3

Phần 3 tạo khối dữ liệu bằng cách truy vấn RAM của chúng tôi giống như phần 3.

Vì lợi ích của sự ngắn gọn, tôi sẽ bỏ qua hầu hết các chi tiết về cách thức hoạt động của phần 3. Nó gần như giống hệt với phần 2 ngoại trừ thay vì dịch từng mốc dữ liệu thành một ký tự, nó dịch mỗi ký tự thành một đoạn mã dài biểu thị mục nhập của nó trong RAM. Khi phần 3 được thực hiện, nó báo cho chương trình thoát khỏi vòng lặp.


Sau khi vòng lặp đã được chạy, chương trình chỉ cần đẩy bit đầu tiên của quine ([()]())(()()()()){({}[(. Tôi thực hiện điều này với đoạn mã sau đây thực hiện các kỹ thuật phức tạp Kolmogorov tiêu chuẩn.

(([(((((()()()()){}){}())){}{})]((({}))([()]([({}())]({}()([()]((()([()]((()([({})((((()()()()){}){}()){})]()())([({})]({}([()()]({}({}((((()()()()()){}){}){}))))))))))))))))))

Tôi hy vọng điều này là rõ ràng. Hãy bình luận nếu bạn bối rối về bất cứ điều gì.


Mất bao lâu để chạy? Nó lần trên TIO.
Pavel

@Pavel Tôi không chạy nó trên TIO vì điều đó sẽ cực kỳ chậm, tôi sử dụng cùng một trình thông dịch mà TIO sử dụng ( ruby ). Mất khoảng 20 phút để chạy trên một máy chủ rack cũ mà tôi có quyền truy cập. Mất khoảng 15 phút trong Crain-Flak, nhưng Crain-Flak không có cờ gỡ lỗi nên tôi không thể ghi điểm mà không chạy nó trong trình thông dịch Ruby.
Phù thủy lúa mì

@Pavel Tôi chạy lại và hẹn giờ. Phải 30m45.284shoàn thành trên một máy chủ cấp thấp (gần tương đương với máy tính để bàn hiện đại trung bình) bằng trình thông dịch ruby.
Phù thủy lúa mì
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.