Mẹo chơi gôn trong Brain-Flak


24

Brain-flak là một ngôn ngữ turing-tarpit dựa trên ngăn xếp, được viết cộng tác giữa tôi, DJMcMayhem1000000000 .

Một số người dùng rất có kinh nghiệm trong những cách bí ẩn của Brain-Flak. Vì vậy, tôi nghĩ rằng nên đặt câu hỏi này như một cách cho chúng tôi và hy vọng những người khác cũng vậy, để chia sẻ kiến ​​thức của chúng tôi với cộng đồng và hạ thấp mục nhập của ngôn ngữ này "được thiết kế để gây đau đớn khi sử dụng". Và thậm chí có thể dạy những người trong chúng ta có nhiều kinh nghiệm hơn một số thủ thuật mới.

Vì vậy, những lời khuyên nào bạn có để chơi golf trong não bộ? Tôi đang tìm kiếm những ý tưởng có thể được áp dụng cho các vấn đề về golf nói chung ít nhất là cụ thể đối với vấn đề não bộ (ví dụ: "xóa bình luận" không phải là một câu trả lời).

Xin vui lòng gửi một lời khuyên cho mỗi câu trả lời.

Câu trả lời:


22

Sử dụng ngăn xếp thứ ba

Nếu bạn đã đọc tiêu đề, bạn có thể hơi bối rối. Chắc chắn chỉ có hai ngăn xếp trong Brain-Flak? Tuy nhiên tôi đảm bảo với bạn rằng nó tồn tại và nó là một trong những công cụ mạnh nhất nếu không phải là công cụ mạnh nhất trong việc viết và chơi gôn Brain-Flak.


"Ngăn xếp thứ ba" là gì?

Mỗi chương trình Brain-Flak sử dụng ngăn xếp thứ ba theo cách này hay cách khác nhưng phần lớn việc sử dụng diễn ra sau hậu trường và thường rất hữu ích khi đơn giản bỏ qua thực tế là nó tồn tại. Mỗi dấu ngoặc trong chương trình sẽ thêm hoặc xóa một mục khỏi ngăn xếp. Ba trong số các dấu ngoặc nhọn ([<đều thêm một mục vào ngăn xếp trong khi ba liên hợp của chúng )]>đều loại bỏ một mục khỏi ngăn xếp. Giá trị của mục trên ngăn xếp là giá trị của phạm vi hiện tại của chương trình và sử dụng nilad sẽ sửa đổi giá trị này theo một số cách nhất định. Dấu ngoặc gần) có chức năng duy nhất là di chuyển một phần tử từ Ngăn thứ ba sang ngăn xếp hiện tại; một cú đẩy.

Hy vọng điều này đang trở nên rõ ràng với bạn. Ngăn xếp thứ ba là một số loại ngăn xếp ghi nhớ các giá trị trả về của mã đã được thực thi. Chúng ta hãy xem qua một ví dụ về một chương trình đơn giản theo dõi hai ngăn xếp thông thường và Ngăn xếp thứ ba.

Thí dụ

Chúng tôi sẽ đi qua chương trình sau đây. Chương trình này đẩy -3, 1, -2đến ngăn xếp.

(([()()()])(()))

Chúng tôi bắt đầu với ba dấu ngoặc mở, tất cả đều đẩy số 0 đến ngăn thứ ba.

Ngăn xếp của chúng ta bây giờ trông như thế này, Ngăn xếp thứ ba là bên phải và ngăn xếp hoạt động có một ngăn xếp ^ bên dưới:

        0
        0
  0  0  0
  ^

(([()()()])(()))
   ^

Bây giờ chúng tôi có ba () nilad. Chúng không làm bất cứ điều gì với hai ngăn xếp thông thường, tuy nhiên chúng thực hiện mỗi lần thêm một vào đầu ngăn xếp thứ ba làm cho các ngăn xếp của chúng ta trông giống như:

        3
        0
  0  0  0
  ^

(([()()()])(()))
         ^

Bây giờ chúng ta bắt gặp một ]như đã nêu trước khi niềng răng chặt loại bỏ một mục khỏi Ngăn xếp thứ ba, nhưng] có chức năng trừ phần tử mà nó loại bỏ khỏi đầu ngăn xếp. Do đó, ngăn xếp mới của chúng tôi sẽ trông như sau:

       -3
  0  0  0
  ^

(([()()()])(()))
          ^

Điều này thật ý nghĩa; [...]không phủ định ]nên trừ đi xuống.

Bây giờ chúng ta phải thực thi a ). Như bạn có khả năng nhớ lại )là vị trí trong chương trình nơi các công cụ được đẩy lên ngăn xếp, vì vậy chúng tôi sẽ di chuyển đỉnh của Ngăn thứ ba sang ngăn xếp hiện tại, ngoài ra chúng tôi sẽ thêm -3phần tử tiếp theo vào ngăn xếp thứ ba.

 -3  0 -3
  ^

(([()()()])(()))
           ^

Một lần nữa, chúng tôi bắt gặp một trong ba dấu ngoặc nhọn mở của chúng tôi vì vậy chúng tôi sẽ thêm một yếu tố khác vào Ngăn xếp thứ ba của chúng tôi.

        0
 -3  0 -3
  ^

(([()()()])(()))
            ^

Như chúng ta đã nói trước đó ()sẽ tăng đỉnh của ngăn xếp thứ ba của chúng ta lên một.

        1
 -3  0 -3
  ^

(([()()()])(()))
              ^

)sẽ di chuyển đỉnh của Ngăn thứ ba lên ngăn xếp hoạt động và thêm xuống dưới

  1
 -3  0 -2
  ^

(([()()()])(()))
               ^

Cái cuối cùng )di chuyển Ngăn xếp thứ ba lên ngăn xếp hoạt động và vì không còn phần tử nào trên Ngăn thứ ba để thêm vào, nên không có gì khác.

 -2
  1
 -3  0
  ^

(([()()()])(()))
                ^

Chương trình kết thúc nên chúng tôi chấm dứt và đầu ra.


Ví dụ này nhằm cung cấp cho bạn cảm giác về Thứ ba là gì và làm gì. Nó không bao gồm tất cả các hoạt động, nhưng hy vọng bạn có thể tự mình tìm ra những gì chúng thực hiện. Nếu bạn vẫn đang vật lộn, tôi đã bao gồm một "chiếc áo choàng" ở cuối câu trả lời này để giúp bạn thực hiện.

Ok, vậy thì sao?

Ok, bây giờ bạn đã hiểu Ngăn xếp thứ ba, nhưng "Vậy thì sao"? Bạn đã sử dụng nó ngay cả khi bạn không gọi nó là "Ngăn thứ ba", làm thế nào để suy nghĩ về Ngăn xếp thứ ba giúp bạn chơi golf?

Hãy nhìn vào một vấn đề. Bạn muốn lấy Tam giác của một số . Đây là tổng của tất cả các số nhỏ hơn n.

Một cách tiếp cận có thể là tạo một bộ tích lũy trên offstack và thêm vào nó khi bạn đếm ngược. Điều này tạo ra mã trông như thế này:

(<>)<>{(({}[()])()<>{})<>}{}<>({}<>)

Hãy thử trực tuyến!

Mã này khá nhỏ gọn và người ta có thể nghĩ rằng nó không thể nhỏ hơn nhiều. Tuy nhiên, nếu chúng ta tiếp cận nó từ quan điểm ngăn xếp thứ ba thì rõ ràng điều này rất kém hiệu quả. Thay vì đặt bộ tích lũy của chúng tôi trên offstack, chúng tôi có thể đặt nó vào ngăn xếp thứ ba với một (và lấy nó ở cuối chúng tôi sử dụng ). Chúng tôi sẽ một lần nữa lặp lại tất cả các con số, nhưng lần này chúng tôi không phải làm gì nhiều để tăng Thứ ba của chúng tôi, chương trình thực hiện điều đó cho chúng tôi. Điều này trông giống như:

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

Dùng thử trực tuyến

Mã này nhỏ hơn một nửa kích thước của phiên bản golf khá tốt mà chúng tôi đã thực hiện trước đó. Trong thực tế, một tìm kiếm trên máy tính đã chứng minh rằng chương trình này là chương trình ngắn nhất có thể thực hiện được nhiệm vụ này. Chương trình này có thể được giải thích bằng cách sử dụng phương pháp "tổng của tất cả các lần chạy", nhưng tôi nghĩ nó trực quan và rõ ràng hơn rất nhiều khi được giải thích bằng cách sử dụng phương pháp Ngăn xếp thứ ba.

Khi nào tôi sử dụng Ngăn xếp thứ ba?

Lý tưởng nhất là bất cứ khi nào bạn bắt đầu làm việc với một vấn đề mới trong Brain-Flak, bạn nên tự suy nghĩ làm thế nào tôi sẽ làm điều này với Thứ ba trong tâm trí. Tuy nhiên, theo nguyên tắc chung, bất cứ khi nào bạn phải theo dõi một số loại tích lũy hoặc có tổng số hoạt động, bạn nên thử đặt nó lên ngăn xếp thứ ba thay vì hai ngăn xếp thực sự.

Một lần khác có thể là một ý tưởng tốt để xem xét sử dụng ngăn xếp thứ ba của bạn là khi bạn không có bất kỳ không gian nào để lưu trữ một số giá trị trên hai ngăn xếp khác. Điều này có thể đặc biệt hữu ích khi bạn thực hiện các thao tác trên hai ngăn xếp hiện có và bạn muốn lưu một giá trị để sử dụng sau này mà không phải theo dõi vị trí của nó.

Hạn chế của ngăn xếp thứ ba

Ngăn xếp thứ ba rất mạnh theo nhiều cách nhưng nó đi kèm với những hạn chế và nhược điểm riêng.

Thứ nhất, chiều cao ngăn xếp tối đa cho Ngăn xếp thứ ba tại bất kỳ điểm nào được xác định tại thời điểm biên dịch. Điều này có nghĩa là nếu bạn muốn sử dụng một lượng không gian trên ngăn xếp, bạn phải phân bổ không gian đó khi bạn đang viết chương trình.

Thứ hai, ngăn xếp thứ ba không phải là truy cập ngẫu nhiên. Điều này có nghĩa là bạn không thể thực hiện bất kỳ thao tác nào trên bất kỳ giá trị nào ngoài giá trị cao nhất. Ngoài ra, bạn không thể di chuyển các giá trị xung quanh trên ngăn xếp (giả sử trao đổi hai phần tử đầu tiên).

Phần kết luận

Ngăn xếp thứ ba là một công cụ mạnh mẽ và tôi sẽ coi nó là thiết yếu cho mọi người dùng Brain-Flak. Nó cần một số quen thuộc và đòi hỏi một sự thay đổi trong cách suy nghĩ của bạn về lập trình trong Brain-Flak, nhưng khi được sử dụng đúng cách, nó sẽ tạo ra sự khác biệt giữa một phong nha và tuyệt vời khi chơi golf.

Áo choàng

Dưới đây là danh sách các hoạt động và cách chúng ảnh hưởng đến Ngăn xếp thứ ba

 Operation  |                 Action
====================================================
   (,[,<    | Put a zero on top of the Third Stack
----------------------------------------------------
     )      | Add the top of the Third Stack to the
            | second element and move it to the 
            | active stack
----------------------------------------------------
     ]      | Subtract the top of the Third Stack
            | from the second element and pop it
----------------------------------------------------
     >      | Pop the top of the Third Stack
----------------------------------------------------
     ()     | Add one to the top of the Third Stack
----------------------------------------------------
     {}     | Pop the top of the active stack and
            | add it to the top of the Third Stack
----------------------------------------------------
     []     | Add the stack height to the Third
            | Stack
----------------------------------------------------
   <>,{,}   | Nothing

1
Wow, mẹo tuyệt vời! Tôi thực sự chỉ đến đây để viết một câu trả lời tương tự khi tôi thấy điều này. +1! Tôi thích nghĩ về nó như The Accumulator , nhưng ẩn dụ The Stack thứ ba thực sự thú vị.
DJMcMayhem

Tôi luôn gọi nó là "không gian làm việc" hay "nơi làm việc".
sagiksp

Trên phiên bản TIO của Brainflak, {...}trả về tổng của tất cả các lần lặp.
Máy

@CalculatorFeline Vâng, điều này đúng trên hầu hết các phiên bản Brain-Flak ngoại trừ một số phiên bản rất sớm. Tuy nhiên tôi không chắc tại sao bạn lại bình luận rằng trên bài đăng này nói riêng.
Phù thủy lúa mì

<>,{,} | Nothing
Máy

21

Tìm mô-đun / phần còn lại

Tìm n modulo m là một trong những phép toán số học cơ bản, quan trọng đối với nhiều thách thức. Đối với các trường hợp m> 0n> = 0 , đoạn mã 46 byte sau có thể được sử dụng. Nó giả định rằng n ở trên cùng của ngăn xếp hoạt động với m tiếp theo xuống và thay thế chúng bằng n mod m . Nó để lại phần còn lại của ngăn xếp nguyên vẹn.

({}(<>))<>{(({})){({}[()])<>}{}}{}<>([{}()]{})

Phiên bản chú thích này hiển thị nội dung ngăn xếp tại một số điểm trong chương trình. ;tách hai ngăn xếp và .đánh dấu ngăn xếp hoạt động.

. n m;
({}(<>))<>
{   . m; r 0   (r = n - km)
    (({}))
    . m m; r 0
    {({}[()])<>}
    {}
}
m-n%m-1 m; . 0
{}<>([{}()]{})
. n%m;

Phải mất một thời gian tôi mới hiểu được phần không được quản lý đã làm gì ( {({}[()])<>}), nhưng một khi tôi đã hiểu ra ... Thiên tài :-)
ETHproductions

11

Dự phòng Push-Pop

Đây là một vấn đề lớn. Nó cũng là một chút của một sắc thái.

Ý tưởng là nếu bạn đẩy một cái gì đó và sau đó bật nó lên mà không làm gì thì bạn không nên đẩy nó ra.

Ví dụ: nếu bạn muốn di chuyển một cái gì đó sang offstack và sau đó thêm một cái vào đó, bạn có thể viết đoạn mã sau:

({}<>)({}())

Điều này có thể đơn giản như thế này:

({}<>())

Chương trình đầu tiên nhặt vật phẩm di chuyển nó, nhặt nó lên một lần nữa và thêm một cái, trong khi chương trình thứ hai thực hiện cả hai trong một cú trượt ngã.

Ví dụ đó đơn giản tuy nhiên nó có thể phức tạp hơn nhiều. Lấy ví dụ:

({}<({}<>)><>)(<((()()()){}[((){}{})])>)

Mức giảm ít rõ ràng hơn ở đây nhưng pop thứ 4 trong chương trình có thể được giảm với lần đẩy thứ 2, như vậy:

(<((()()()){}[((){}<({}<>)><>{})])>)

Đây là công cụ mạnh nhất trong các tiết mục chơi gôn của tôi nhưng cần có một số thực hành để có thể làm tốt nó. Một khi bạn đã làm những việc này một thời gian, bạn sẽ có thể phát hiện ra những điều này gần như ngay lập tức.


Trong ví dụ sau, không phải là phần trong cặp dấu ngoặc đơn đầu tiên tương đương với ({}<{}>)?
frageum

@feersum Không, không phải vậy. Nó di chuyển một bản sao của mục thứ hai trên ngăn xếp sang ngăn xếp tắt trong khi ({}<{}>)phá hủy hoàn toàn mục đó. Tuy nhiên, các chương trình này không thực sự có nghĩa là tối ưu chỉ để làm nổi bật nguyên tắc làm việc ở đây.
Thuật sĩ lúa mì

6

Tối ưu hóa số nguyên của bạn

Số nguyên là tẻ nhạt để đại diện trong Brain-Flak. May mắn thay, chúng tôi có một câu hỏi sẽ giúp Golf một Integer Brain-Flak cho bạn. (Lưu ý rằng câu hỏi được thiết kế để đẩy số nguyên lên ngăn xếp, do đó, dự phòng đẩy pop có thể áp dụng cho các chương trình thực tế hơn.)


Lưu ý rằng chúng tôi cũng có brain-flak.github.io/integer/ , chạy một trong những thuật toán trực tuyến để thuận tiện.
DJMcMayhem

@DrMcMoylex Tất cả chúng ta cần bây giờ là khi thực hiện metagolfer số nguyên trong chính Brain-Flak!
Neil

1
Chúng tôi có điều đó. codegolf.stackexchange.com/a/98054/31716 : D
DJMcMayhem

5

Đẩy quầy vòng lặp thêm

Thường xuyên, bạn sẽ muốn làm một cái gì đó như

Thực hiện thao tác X trên mọi phần tử trong ngăn xếp

hoặc là

Thực hiện thao tác X trên mọi cặp phần tử lân cận trong ngăn xếp

Khi đầu vào có thể chứa '0' hoặc kết quả của hoạt động X có thể cho 0, điều này thực sự bất tiện. Bởi vì bạn sẽ cần phải làm:

([])
{

  {}

  ({}...<>)
  ([])

}

Làm X cho từng phần tử, và sau đó

<>
([])
{
  {}
  ({}<>)
  <>
  ([])
}
<>

Để đảo ngược mảng một lần nữa.

Thậm chí tệ hơn, nếu chúng ta vận hành theo các cặp yếu tố liền kề, chúng ta sẽ cần phải ([][()])thay thế ([]). Điều này thực sự bất tiện. Đây là mẹo: Trong khi bạn thực hiện X cho từng phần tử, hãy nhấn 1 lên trên ngăn xếp thay thế ngay phía trên X(element). Sau đó, trong khi bạn đảo ngược nó, bạn có thể chỉ cần làm

<>
{
  {}
  ({}<>)
  <>
}
<>

Đây là ngắn hơn 8 byte, vì vậy khi bạn tính đến mã bổ sung để đẩy 1, cuối cùng bạn sẽ tiết kiệm được 4 - 6 byte. Để có một ví dụ cụ thể hơn, hãy so sánh nhiệm vụ lấy deltas của một mảng. Nếu không có mẹo này, bạn cần:

([][()]){

    {}

    ([{}]({})<>)<>

    ([][()])

}{}{}<>

([])
{{}({}<>)<>([])}<>

Đối với 62. Với thủ thuật này, bạn sẽ có

([][()]){

    {}

    ([{}]({})<>)(())<>

    ([][()])

}{}{}<>

{{}({}<>)<>}<>

Đối với 58. Nếu được sử dụng đúng cách (ví dụ: đảo ngược trước và loại bỏ hai ([][()])từ sau), điều này có thể tiết kiệm nhiều hơn trong các kịch bản speficic.


3

Tận dụng lợi thế của nilad 'Stack-height'

Đặc biệt là trong thử thách hoặc các thử thách mà bạn luôn biết kích thước của đầu vào, bạn có thể tận dụng lợi thế của nilad 'Stack-height'[] để tạo ra các số nguyên.

Chúng ta hãy giải quyết vấn đề này với một thách thức giả định: đầu ra CAT. Cách không chơi gôn là sử dụng người chơi số nguyên trực tuyến để đẩy 67, 65 và 84. Điều này mang lại:

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

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

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

(Dòng mới cho rõ ràng). Đây là 88 byte, và không phải là tuyệt vời. Thay vào đó, nếu chúng ta thay đổi sự khác biệt liên tiếp giữa các giá trị, chúng ta có thể tiết kiệm rất nhiều. Vì vậy, chúng tôi bọc số đầu tiên trong một cuộc gọi đẩy và trừ 2:

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

Sau đó, chúng tôi lấy mã này, bọc nó trong một cuộc gọi đẩy và thêm 19 vào cuối:

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

Đây là 62 byte, cho một sân golf 26 byte!

Bây giờ đây là nơi chúng ta có thể tận dụng lợi thế của nilad stack-height. Khi chúng tôi bắt đầu đẩy 19, chúng tôi biết rằng đã có 2 mục trên ngăn xếp, vì vậy []sẽ đánh giá 2. Chúng ta có thể sử dụng điều này để tạo 19 trong ít byte hơn. Cách rõ ràng là thay đổi bên trong ()()()để ()[]. Điều này chỉ tiết kiệm hai byte mặc dù. Với một số tiếng leng keng hơn, hóa ra chúng ta có thể đẩy 19 với

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

Điều này giúp chúng tôi tiết kiệm 6 byte. Bây giờ chúng tôi xuống còn 56.

Bạn có thể thấy mẹo này đang được sử dụng rất hiệu quả cho những câu trả lời sau:


CATChương trình của bạn thực sự đẩy mạnh TAC: P
Wheat Wizard


2
Tôi biết đây là một mẹo nhưng tôi không thể tự giúp mình, 50 byte .
Thuật sĩ lúa mì

1
Một mẹo kỳ lạ nhưng đôi khi hiệu quả để giúp lạm dụng []: trả trước 0s với (<>)s trước mã của bạn. Điều này chỉ thực sự hoạt động nếu bạn dự định đẩy mã ngược lại, nhưng nếu bạn gặp đúng số, bạn có thể giảm mã của mình xuống một chút. Một ví dụ khá khắc nghiệt khi tôi nối thêm 6 0giây, cho phép tôi sử dụng nhiều []như tôi sử dụng()
Jo King

2

Sử dụng wiki

Chúng tôi có một wiki ! Nó có một số comings ngắn, nhưng nó là tài nguyên hữu ích. Nó có danh sách các chương trình hữu ích, được chơi tốt, xếp chồng sạch mà bạn có thể dán vào mã của mình. Nếu bạn muốn làm một cái gì đó bạn nghĩ rằng ai đó có thể đã làm trước khi có một cơ hội tốt đó là trên wiki.


2

Vòng lặp tốt hơn

Đây là một điều dễ dàng:

Một cấu trúc khá phổ biến là:

(({})<{({}[()]<...>)}{}>)

Nơi bạn muốn lặp n lần nhưng vẫn giữ n. Tuy nhiên, điều này có thể được viết là:

({<({}[()]<...>)>()}{})

để lưu 2 byte.

Một mô hình khá phổ biến khác là

([])({<{}>...<([])>}{})

Mà sẽ lặp và tích lũy toàn bộ ngăn xếp. Do một số phép toán ưa thích, điều này giống như:

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

1

Kiểm tra tiêu cực của bạn

Đôi khi bạn có thể chơi gôn một vài byte bằng cách chiến lược chọn những gì cần phủ định với [...]đơn nguyên.

Một ví dụ đơn giản là trong [...]s lồng nhau . Ví dụ [()()()[()()]]chỉ có thể là[()()()]()()

Giả sử bạn muốn kiểm tra xem một giá trị có phải là bất kỳ dấu ngoặc bắt đầu nào không (<{[. Nỗ lực ban đầu là đẩy sự khác biệt giữa mỗi nhân vật và lặp đi lặp lại trừ đi

({}<(((((       #Push the differences between start bracket characters
(((()()()()){}){}){})   #Push 32
[()])                   #Push 31
[((()()())()){}{}])     #Push 20
){})><>)                #Push 40
<>({<(([{}]<>{}))>(){[()](<{}>)}<>})

Tuy nhiên, thay vào đó, bạn có thể tiết kiệm 4byte bằng cách đẩy các phiên bản tiêu cực của sự khác biệt!

({}<(((((       #Push the differences between start bracket characters
((([()()()()]){}){}){}) #Push -32
())                     #Push -31
((()()())()){}{})       #Push -20
){})><>)                #Push -40
<>({<(({}<>{}))>(){[()](<{}>)}<>})

Nói chung, điều này không giúp bạn tiết kiệm rất nhiều, nhưng cũng tốn rất ít công sức để thay đổi xung quanh những gì [...]xung quanh. Hãy cảnh giác với các tình huống mà bạn có thể đẩy tiêu cực của bộ đếm thay vì tích cực để tiết kiệm tăng lên nhiều lần thay vì giảm dần sau đó. Hoặc hoán đổi (a[b])với ([a]b)để làm cho sự khác biệt giữa hai số âm thay vì tích cực.


1
Những điều tương tự có thể được thực hiện với số không <a<b>c>-> <abc><a[b]c>-> <abc>.
Thuật sĩ 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.