Viết một thông dịch viên


10

EDIT: Như một số bạn nghi ngờ, có một lỗi trong trình thông dịch chính thức: thứ tự sáng tác .đã bị đảo ngược. Tôi đã có hai phiên bản của trình thông dịch, và đã sử dụng sai phiên bản ở đây. Các ví dụ cũng được viết cho phiên bản không chính xác này. Tôi đã sửa trình thông dịch trong kho lưu trữ và các ví dụ bên dưới. Mô tả về >nó cũng hơi mơ hồ, vì vậy tôi đã sửa nó. Ngoài ra, lời xin lỗi cho việc này mất quá nhiều thời gian, tôi đã bị cuốn vào một số thứ thực tế trong cuộc sống.

EDIT2: Trình thông dịch của tôi có một lỗi trong việc triển khai .được phản ánh trong các ví dụ (họ dựa vào hành vi không xác định). Vấn đề hiện đã được khắc phục.

Giới thiệu

Shift là một ngôn ngữ lập trình chức năng bí truyền mà tôi đã tạo ra vài năm trước, nhưng được xuất bản ngày hôm nay. Nó dựa trên stack, nhưng cũng có tính năng currying tự động như Haskell.

Sự chỉ rõ

Có hai kiểu dữ liệu trong Shift:

  • Chức năng, trong đó có một dương tùy ý arity (số đầu vào), và đó trả về một danh sách các kết quả đầu ra. Ví dụ, một chức năng sao chép đầu vào duy nhất của nó có arity 1 và một chức năng hoán đổi hai đầu vào của nó có arity 2.
  • Các khoảng trống, tất cả đều giống hệt nhau và không có mục đích nào khác ngoài việc không phải là các chức năng.

Một chương trình Shift bao gồm 0 hoặc nhiều lệnh , mỗi lệnh là một ký tự ASCII. Tổng cộng có 8 lệnh:

  • !( áp dụng ) bật một hàm fvà một giá trị xtừ ngăn xếp và áp dụng fcho x. Nếu fcó arity 1, danh sách f(x)được thêm vào phía trước ngăn xếp. Nếu nó có arity n > 1, một (n-1)hàm -ary mới gđược đẩy lên ngăn xếp. Nó có đầu vào và trả lại .x1,x2,...,xn-1f(x,x1,x2,...,xn-1)
  • ?( trống ) đẩy một khoảng trống vào ngăn xếp.
  • +( clone ) đẩy vào ngăn xếp một hàm unary trùng lặp đầu vào của nó: bất kỳ giá trị nào xđược ánh xạ tới [x,x].
  • >( shift ) đẩy vào ngăn xếp một hàm unary có chức năng n-ary fvà trả về một (n+1)hàm -ary gmà bỏ qua đối số đầu tiên của nó x, gọi fcác hàm còn lại và xử lý xtrước kết quả. Ví dụ, shift(clone)là một hàm nhị phân nhận đầu vào a,bvà trả về [a,b,b].
  • /( ngã ba ) đẩy vào ngăn xếp một hàm ternary có ba đầu vào a,b,cvà trả về [b]nếu alà một khoảng trống, và [c]nếu không.
  • $( cuộc gọi ) đẩy vào ngăn xếp một hàm nhị phân bật ra một hàm fvà một giá trị x, và áp dụng fcho xchính xác như !vậy.
  • .( chuỗi ) đẩy vào ngăn xếp một hàm nhị phân bật ra hai hàm fgtrả về thành phần của chúng: một hàm hcó cùng mức với f, và lấy đầu vào của nó một cách bình thường, áp dụng fcho chúng, và sau đó áp dụng hoàn toàng cho kết quả (gọi nó nhiều lần như arity của nó ra lệnh), với các mục không được sử dụng từ đầu ra fcòn lại trong kết quả của h. Ví dụ, giả sử đó flà một hàm nhị phân nhân bản đối số thứ hai của nó và gcuộc gọi . Nếu ngăn xếp chứa [f,g,a,b,c]và chúng ta làm .!!, thì nó chứa [chain(f,g),a,b,c]; Nếu chúng ta làm !!tiếp theo, thì flần đầu tiên được áp dụng a,b, sản xuất[a,b,b], sau đó gđược áp dụng cho hai yếu tố đầu tiên vì tính chất của nó là 2, tạo ra [a(b),b]và ngăn xếp cuối cùng sẽ là [a(b),b,c].
  • @( nói ) đẩy một hàm unary đơn giản trả về đầu vào của nó và in 0nếu nó là một khoảng trống và 1nếu đó là một hàm.

Lưu ý rằng tất cả các lệnh ngoại trừ !chỉ đơn giản là đẩy một giá trị vào ngăn xếp, không có cách nào để thực hiện đầu vào và cách duy nhất để xuất bất cứ thứ gì là sử dụng @. Một chương trình được diễn giải bằng cách đánh giá từng lệnh một, in 0s hoặc 1s bất cứ khi nào "say" được gọi và thoát. Bất kỳ hành vi nào không được mô tả ở đây (áp dụng khoảng trống, áp dụng một ngăn xếp có độ dài 0 hoặc 1, gọi "chuỗi" trên một khoảng trống, v.v.) là không xác định: trình thông dịch có thể bị sập, thất bại âm thầm, yêu cầu nhập liệu hoặc bất cứ điều gì.

Nhiệm vụ

Nhiệm vụ của bạn là viết một trình thông dịch cho Shift. Nó sẽ lấy từ STDIN, dòng lệnh hoặc đối số chức năng một chương trình Shift sẽ được diễn giải và in ra STDOUT hoặc trả về kết quả đầu ra (có thể là vô hạn) của 0s và 1s. Nếu bạn viết một hàm, bạn phải có khả năng truy cập các đầu ra có độ dài vô hạn theo một cách nào đó (trình tạo trong Python, danh sách lười biếng trong Haskell, v.v.). Ngoài ra, bạn có thể lấy một đầu vào khác, một số nvà trả lại ít nhất các nký tự của đầu ra nếu nó dài hơn n.

Số byte thấp nhất sẽ thắng và các sơ hở tiêu chuẩn không được phép.

Các trường hợp thử nghiệm

Chương trình Shift này in 01:

?@!@@!

Bắt đầu từ bên trái: đẩy một khoảng trống, đẩy nói , sau đó áp dụng nói vào chỗ trống. Kết quả này 0. Sau đó, đẩy nói hai lần, và áp dụng lần nói thứ hai cho lần đầu tiên. Kết quả này 1.

Chương trình này lặp lại mãi mãi, không tạo ra đầu ra:

$+.!!+!!

Đẩy cuộc gọisao chép , sau đó áp dụng chuỗi cho chúng (chúng ta cần hai !s vì chuỗi là hàm nhị phân). Bây giờ ngăn xếp chứa một hàm lấy một đối số, sao chép nó và gọi bản sao đầu tiên trên thứ hai. Với +!!, chúng tôi nhân đôi chức năng này và gọi nó trên chính nó.

Chương trình này in 0010:

?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!

Đẩy một cái trống và nói . Sau đó, soạn một hàm nhị phân sao chép đối số thứ hai của nó b, sau đó sao chép hàm thứ nhất avà tự kết hợp nó với chính nó, sau đó áp dụng thành phần cho bản sao của b, trả về [a(a(b)),b]. Áp dụng nó để nói và để trống, sau đó áp dụng nói cho hai yếu tố còn lại trên ngăn xếp.

Chương trình này in 0. Đối với mỗi !!!cái mà bạn gắn vào nó, nó sẽ in thêm 0.

?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!

Đẩy một cái trống và nói . Sau đó, soạn một hàm ternary lấy f,g,xđầu vào và trả về [f,f,g,g(x)]. Nhân bản chức năng đó, và áp dụng nó cho chính nó, nói và trống. Ứng dụng này không thay đổi ngăn xếp, vì vậy chúng ta có thể áp dụng lại chức năng nhiều lần như chúng ta muốn.

Chương trình này in chuỗi vô hạn 001011011101111..., trong đó số 1s luôn tăng thêm một:

@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!

Các kho chứa một phiên bản chú thích.


Tôi có một chút bối rối ở đây. Khi bạn viết "Take in" như trong lệnh shift, bạn có nghĩa là bật hoặc bạn có nghĩa là được áp dụng bởi lệnh áp dụng?
twordwiz121

1
Ngoài ra, tôi thực sự không chắc chắn từ thông số kỹ thuật của bạn về cách thức hoạt động của chuỗi. Bạn có thể làm rõ nó với một ví dụ xin vui lòng?
twordwiz121

@ tecywiz121 Đây là cách tôi hiểu nó: giả sử bạn có hai chức năng trên đỉnh của ngăn xếp f(x1, x2, ..., xn)g(y1, y2, ..., ym). Gọi .bật cả hai và đẩy một chức năng h(z1, z2, ..., zn). Bây giờ bạn có thể ăn hết những lý lẽ đó bằng cách dần dần chế biến nó !. Sau ncác ứng dụng như vậy, hàm còn lại chỉ có một đối số và tại thời điểm đó, nó sẽ tính toán f(z1, z2, ..., zn)(nghĩa là fáp dụng cho tất cả các đối số mà bạn đã sử dụng), đẩy một số giá trị mới, sau đó sử dụng ngay mcác giá trị từ ngăn xếp và gọi gchúng.
Martin Ender

@ MartinBüttner Nếu Zgarb nghĩ rằng nó phù hợp với các quy tắc bạn có thể sử dụng tham số đầu vào thứ hai xác định kích thước tối đa của đầu ra. Đây cũng sẽ là một giải pháp cho vấn đề đánh giá lười biếng.
Randomra

@ tecywiz121 .hoạt động chính xác như Martin mô tả, ngoại trừ việc nếu ftrả về một danh sách nhỏ hơn mcác giá trị, kết quả không được xác định (chế phẩm có arity n, vì vậy nó không thể ăn thêm đối số từ ngăn xếp). Về cơ bản, đầu ra của fđược sử dụng như một ngăn xếp tạm thời, trên đó gđược đẩy và áp dụng mthời gian sử dụng !, và kết quả của nó được thêm vào ngăn xếp chính.
Zgarb

Câu trả lời:


12

Python 2, 752 667 534 506 445 436 427 404 398 393 byte

Điều này không có nghĩa là ngắn ... nhưng tôi đã làm hết sức mình. Bất kỳ đề xuất chơi golf nào sẽ được đánh giá rất cao ...

EDIT6: Đây là một tập lệnh thay vì một chức năng. Lưu nó vào một tệp (shift.py, forex), sau đó chạy nó với $ python shift.py '<my_input>'. Đảm bảo đặt đầu vào trong dấu ngoặc đơn, hoặc bash sẽ phát điên với các ký tự đầu vào.

EDIT7: Aaaaaaand ... nó không thể đọc được nữa. Nhưng tôi đã loại bỏ thêm 23 byte, vậy thì tốt quá, tôi đoán vậy? Tôi cũng sẽ đăng một phiên bản không có bản quyền.

EDIT8: Thêm một golf nữa, nhờ @Zgarb.

k,d=[],[]
u=k.append
def z(f,a=1):f.a=a;return f
exec "i=!x:x(*map(k.pop,[-1]*x.a)));e=dict(zip('?+>/$.@',[0,!x:u(x)<u(x)),!x:u(!a,*_:x(*_)<u(a),x.a+1))),!x,y,z:u((z,y)[x<1]),3),!x,y:u(!*_:x(y,*_),x.a-1))if x.a>1 else x(y),2),!x,y:u(!*_:x(*_)<i(y),x.a)),2),!x:d.append(`+(x>0)`)<u(x))]))".replace('!',"z(lambda ")
for _ in raw_input():
 try:[i,u][_ in e](e.get(_,e['$']))
 except:break
print d

EDIT: cảm ơn @DLosc vì sự giúp đỡ chơi gôn! Quản lý để giảm nó 85 byte.

EDIT2: cắt ra một tấn các hàm bao không cần thiết và bỏ nó thêm 133 byte!

EDIT3: ... và 28 lần nữa nhờ @ Sp3000 và @orlp trong trò chuyện!

EDIT4: với sự trợ giúp từ @orlp & @ Sp3000, đã xóa tất cả các trình trang trí và giờ nó ngắn hơn 61 byte.

EDIT5: giúp tôi, tôi không thể dừng việc đánh gôn này .... thêm 9 byte nữa. Loại bỏ câu lệnh in cuối cùng sẽ tiết kiệm được 7, nhưng sau đó nếu bạn chạy m () trong một vòng lặp, tất cả đầu ra nằm trên cùng một dòng ... điều đó có ổn không?

Đây là một phiên bản chưa được chỉnh sửa:

stack = []
push = stack.append

def arity(func,a=1): #give each of our functions an arity
    func.arity = a
    return func

def do(func): ##pop the args off the stack, then call the function
    args = map(stack.pop,[-1]*func.arity)
    func(*args)

def call(func,arg): #apply is just do(call)
    if func.arity == 1:
        func(arg)
    else:
        def curried(*a): #a quick little currier
            func(arg, *a)
        curried = arity(curried, func.arity - 1)
        push(curried)

def clone(arg):
    push(arg)
    push(arg)

def shift(func):
    def shifted(a, *arg):
        func(*arg)
        push(a)
    shifted = arity(shifted, func.arity + 1)
    push(shifted)

def fork(a, b, c):
    if a == 0:
        push(b)
    else:
        push(c)

def chain(func, gunc):
    def composition(*args):
        func(*args)
        do(gunc)
    composition = arity(composition, func.arity)
    push(composition)

def say(arg):
    print '10'[arg == 0],
    push(arg)

commands = {'?': 0,
            '+': arity(clone),
            '>': arity(shift),
            '/': arity(fork, 3),
            '$': arity(call, 2),
            '.': arity(chain, 2),
            '@': arity(say)}

def interpret(input_string):
    for command in input_string:
        try:
            if command == '!':
                do(call)
            else:
                push(commands[command])
        except RuntimeError: #this handles max recursion depth errors
            break            # for infinite programs
    print

if __name__ == "__main__":
    interpret(raw_input())

Ý tưởng cơ bản là danh sách python hoạt động rất độc đáo như một ngăn xếp, và bằng cách lưu trữ u=k.append, tôi không chỉ lưu các ký tự mà còn có thể sử dụng @unhư một công cụ trang trí cho các chức năng đẩy (không còn nữa!).

Vì một vài hàm hoạt động trên các hàm của n-arity cần có thể chấp nhận một số lượng đối số tùy ý, tôi phải sử dụng *args, điều đó có nghĩa là kế hoạch theo dõi ban đầu của tôi là f.func_code.co_argcount phải được thay thế bằng một arity thuộc tính trang trí .

Về mặt xử lý các chương trình vô hạn, trình thông dịch chạy cho đến khi nó đạt độ sâu đệ quy tối đa; trình xử lý RuntimeError ở phía dưới có nó thoát ra lặng lẽ tại điểm đó và sau đó nó in ra chuỗi đầu ra hiện tại.

Các trường hợp thử nghiệm:

>>> tests
['?@!@@!', '$+.!!+!!', '?@$..!!+.!!+>!.!!!!+?/!!!@!@>!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!!!!!!!', '@?/!@>!.!!??/!!>!.!!+.!!.+>!.!!$$.!!$.!!$.!!+.!!$>!>!.!!$>!>!.!!+>!.!!$>!>!>!.!!+>!>!.!!///!!>!>!>!.!!+!!!!!']
>>> for t in tests: m(t)
0 1

0 0 1 0
0
0 0
0 0 0
0 0 0 0
0 0 1 0 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

1
Phản ứng đầu tiên của tôi: @ _ @ Nghiêm túc, công việc tốt - đưa các chức năng thực tế vào ngăn xếp là một giải pháp thực sự gọn gàng. Một vài lời khuyên: 1) Toán tử ternary thường có thể được rút ngắn bằng cách này hay cách khác . 2) Bạn có thể thay thế ['1','0'][...]bằng chỉ '10'[...]. 3) Tại sao x is 0và không x==0(hoặc x<1)? 4) Đừng bận tâm chỉ định RuntimeError, chỉ cần exceptlàm. 5) Vì bạn đang sử dụng Python 2, các tab và khoảng trắng được tính là các mức thụt đầu dòng khác nhau - nhưng sẽ giúp bạn tiết kiệm ~ 25 byte.
DLosc

1
Bạn sẽ có thể cắt nó thành x.a==1and x(y)or u(a(x.a-1)(b.partial(x,y)))- các toán tử logic vẫn bị đoản mạch, nhưng sử dụng ít ký tự hơn so với ternary. Sau đó lưu một byte khác bằng cách sử dụng x.a-1làm điều kiện (0 / false nếu xlà 1, khác 0 / true nếu không) và hoán đổi các biểu thức 'then' và 'other' : x.a-1and u(a(x.a-1)(b.partial(x,y)))or x(y). (Bây giờ tôi phải chơi golf thêm một chút nữa khi bạn đã vượt qua tôi ...; ^))
DLosc

1
Sau khi gặp vấn đề tương tự với tôi, tôi hiểu những gì thất bại bây giờ - nếu x.a==1là sự thật nhưng x(y)trả lại một cái gì đó sai lệch, nó cũng cố gắng đánh giá u(...). Nhưng có vẻ như bạn không cần phải lưu 3 byte mà sẽ cung cấp cho bạn! Tôi thừa nhận, thưa ông: bạn đã vượt qua tôi.
DLosc

1
Chỉ cần một ngụy biện: định dạng đầu ra được chỉ định không có khoảng trắng - bạn có thể giải quyết bằng nhiều chiến lược khác nhau , không chắc chắn cái nào ngắn nhất. Tất nhiên, chương trình của bạn xử lý RuntimeErrortrong khi của tôi chỉ yêu cầu người dùng chuyển hướng stderr ... vì vậy chúng tôi thậm chí có thể phân biệt được. ; ^)
DLosc

1
Những gì *_trong lambdas là gì?
mbomb007

4

Bản thảo

Chưa chơi gôn, vì tôi vẫn cần phải thực hiện chức năng phân tích cú pháp.

Việc thực hiện này sử dụng _:thay vì >/, và nó yêu cầu tất cả các ký tự chương trình được phân tách bằng dấu cách. Điều này là do >/không phải là tên hợp lệ trong Postcript và các toán tử không tự phân định, nhưng điều này sẽ được sửa khi tôi viết trình phân tích cú pháp.

Phần đầu tiên của mã phải khá minh bạch, vì nó chỉ lặp lại các định nghĩa của các hàm toán tử. Điều kỳ diệu xảy ra trong định nghĩa của !.

/switch {
    /y exch def
    /x exch def
    {x} {y} ifelse
} bind def

/unwrap {
    dup type (arraytype) eq {aload pop} if
} bind def

/! { % IN: <x> <f> OUT: <g>|<f(x)>
    [ 3 1 roll unwrap] cvx %prefix argument into function
    dup /fun exch def %bind

    [ count 1 roll ] { %run the function sandboxed so it can't take any additional args
        2 dict begin
        /=only {} def % suppress output
            {
                fun
            } stopped /err exch def clear err
        end
    } .runandhide


    exch {
        $error /errorname get
        (stackunderflow) ne {
            handleerror
        } if

        $error /newerror false put

        unwrap
    } {
        unwrap exec
    } ifelse
} def

/? 0 def
/+ {{dup}} def
/_ {{/f exch def pop f}} def % using _ instead of >
/: {{? ne 3 1 roll switch}} def % using : instead of /
/$ {{!}} def
/. {{/g exch def exec g}} def 
/@ {{dup ? eq {0}{1} ifelse =only}} def

Cách !hoạt động rất đơn giản: Đầu tiên, nó cho biết thêm lập luận xđể fbằng cách đặt trước xcác nội dung của f, đẩy nó trở lại trên stack, và đặt tên một bản sao của kết quả fun.

Sau đó nó kết thúc toàn bộ ngăn xếp thành một mảng. .runandhidelà một phần mở rộng Ghostscript để chạy mã hộp cát, ẩn nội dung của mảng trước khỏi thủ tục mà nó được gọi. Các dictlệnh đẩy một cuốn từ điển mới trên dict ngăn xếp, thu hẹp phạm vi được xác định tên trong cho đến khi endpops nó quay trở lại. Nó cũng thay thế =only(toán tử đầu ra mà tôi sử dụng @) bằng một hình nộm, triệt tiêu đầu ra trong quá trình chạy thử. stoppedlà PostScript tương đương với trycâu lệnh được tìm thấy trong các ngôn ngữ khác và nó trả về true nếu thủ tục của nó gây ra lỗi và sai nếu nó chạy đến khi hoàn thành.

Khi quá trình chạy thử funhoàn thành, chương trình sẽ khôi phục ngăn xếp ban đầu từ mảng ẩn và nếu funhoàn thành không có lỗi, thì nó sẽ chạy thực sự, giữ nguyên đầu ra.


2

Python3, 685 670 634 633 byte

Tôi khá chắc chắn đây là điều dài nhất tôi từng chơi golf. Nó từng có thể đọc được một chút, nhưng làm theo lời khuyên của @ sirpercival đã loại bỏ nhược điểm đó !

from re import*
E,*k="E"
P="e(k.pop(),k.pop())"
def H(a,b):global k;k+=list(a)+[N(b)];exec("k+=%s;"%P*Z(N(b)));return[]
def e(a,b):a=sub("(?<!\d)0",repr(N(b,1)).replace("\\",r"\\"),a,1);return Z(a)and[a]or list(eval(a))
D=list(zip("ilhydsSNZ",[3,2,2]+[1]*6,sub("A","N(a)",',b,c:[N([b,c][a>E])]|,b:e(A,N(b))|,b:["H(%s,%s)"%(A,repr(b))]|:print(0+(a>E),end="")or[A]|:[A]*2|:["S(0,%s)"%A]|,b:b+[A]|,b=-1:sub("\d+",lambda m:str(int(m.group())+b),a)|:len(split("\D0",a))-1').split("|")))
for n,r,f in D:exec(n+"=lambda a"+f)
F=dict(zip("/$.@+>?!",D))
for z in input():n,r,f=F[z];k+=z!="!"and[[n+"(%s)"%",".join("0"*r),E][z=="?"]]or eval(P)

klà ngăn xếp, chứa các hàm được biểu diễn dưới dạng các chuỗi như "h(0,0)"(đó là c h ain ). Khi một hàm được truyền dưới dạng đối số cho hàm khác, nó sẽ được repr'd và tất cả các số tăng dần : "h('h(1,1)',0)". Khi tất cả các 0s được thay thế trong một hàm, toàn bộ điều được chuyển đến eval, do đó gọi hàm Python thích hợp - hầu hết trong số đó là các hàm lambda được tạo từ chuỗi lớn trong dòng 6 bằng execdòng 7.

Bắt nhiều cấp độ của các hàm lồng nhau tăng lên, trích dẫn và thoát đúng cách là vấn đề đau đầu nhất. Tôi có thể tiết kiệm thêm một chút cho các hoạt động regex nếu tôi có thể cho rằng chức năng lồng nhau sẽ không tiến hành sâu hơn 9 cấp độ, nhưng như đã chỉ ra trong các nhận xét có lẽ không phải là một giả định an toàn.

Ungolfed phiên bản trước của mã:

from re import *
E="E"
stack=[]

clone=lambda a:[unnest(a)]*2
shift=lambda a:["shifted(0,%s)"%unnest(a)]
fork=lambda a,b,c:[unnest(c if a!=E else b)]
call=lambda a,b:apply(unnest(a),unnest(b))
chain=lambda a,b:["chained(%s,%s)"%(unnest(a),repr(b))]
def say(a):
 print(1 if a!=E else 0,end="")
 return [unnest(a)]

shifted=lambda a,b:b+[unnest(a)]
def chained(a,b):
 global stack
 stack+=list(a)+[unnest(b)]
 exec("stack+=apply(stack.pop(),stack.pop());"*zeros(unnest(b)))
 return []

nest=lambda a,direction:sub("\d+",lambda m:str(int(m.group())+direction),a)
unnest=lambda a:nest(a,-1)
zeros=lambda a:len(split("\D0",a))-1
def apply(a,b):
 a=sub("(?<!\d)0",repr(nest(b,1)).replace("\\",r"\\"),a,1)
 return [a] if zeros(a) else list(eval(a))

functions=dict(zip("+>/$.@",zip(["clone","shift","fork","call","chain","say"],[1,1,3,2,2,1])))

for cmd in input():
 if"!"==cmd:
  stack+=apply(stack.pop(),stack.pop())
 elif"?"==cmd:
  stack+=[E]
 else:
  name,arity=functions[cmd]
  stack+=[name+"(%s)"%",".join("0"*arity)]

Một lỗ hổng tiềm năng của việc triển khai này là nó sử dụng đệ quy, vì vậy các chương trình phải là vô hạn đạt đến độ sâu đệ quy tối đa khá nhanh. (Bạn có thể muốn chuyển hướng stderr khi bạn chạy một chương trình vô hạn - nếu không thì dấu vết ngăn xếp sẽ tràn ra đầu ra thực tế.) Khác với điều đó, mọi thứ dường như đang hoạt động.


Bạn có thể viết một chương trình tạo ra chương trình trên và sau đó thực hiện nó không? Bạn có rất nhiều mã định kỳ nên có thể nén như lambda ak.pop().
mbomb007

@ mbomb007 ... Tôi nghĩ não tôi sẽ nổ tung. (Nhưng thấy chỉnh sửa gần đây - tôi đã k.pop()tình hình một chút ít lặp đi lặp lại, dù sao.)
DLosc

bạn có thể thực hiện thủ thuật exec / dịch cho tất cả những lambdas không? dính tất cả trong một chuỗi?
sirpercival

một nhận xét khác: Tôi nghi ngờ bạn có thể dựa vào chức năng lồng nhau <= 9 với ngôn ngữ này
sirpercival

@sirpercival Vâng, tôi đã nghĩ đến việc thử nó. Và không, tôi cho là không. : ^ P
DLosc

1

Tích Lan, 1167 1057 1031

Tôi không hiểu nó ngắn như các phiên bản python đơn ...

import ceylon.language.meta.model{N=Function}import ceylon.collection{H=HashMap}interface D of F|b{}object b satisfies D{}class F(shared Integer a,[D+](D+)f,[D*]c=[])satisfies D{shared[D+]o(D i){[D+]s=[i].prepend(c);return a==1then f(*s)else[F(a-1,f,s)];}shared[D+]y([D+]i){return f(*i.prepend(c));}}F m<A>(N<[D+],A>f)given A satisfies[D+]=>F(f.parameterTypes.size,(D+i)=>f.apply(*i));[D,D]e(D x)=>[x,x];[F]t(F f){[D+]g(D+i){assert(is[D+]r=i.rest);return[i[0],*f.y(r)];}return[F(f.a+1,g)];}[D]k(D a,D d,D c)=>a==b then[d]else[c];[D+]l(F a,D x)=>a.o(x);[F]n(F f,F g){[D+]h(D+i){[D+]r=f.y(i);assert(is[D+]d=r[0:g.a]);return g.y(d).append(r[g.a...]);}return[F(f.a,h)];}[D]y(D x){process.write(x==b then"0"else"1");return[x];}class I(){variable D[]s=[];value c=H{'?'->b,'+'->m(`e`),'>'->m(`t`),'/'->m(`k`),'$'->m(`l`),'.'->m(`n`),'@'->m(`y`)};shared void r(Character i){if(i=='!'){assert(is F f=s[0],is D x=s[1]);s=f.o(x).append(s[2...]);}else{assert(is D d=c[i]);s=[d].append(s);}}}shared void z(){process.readLine()?.collect(I().r);}

Đây là một phiên bản được định dạng (và nhận xét) của cùng một mã (với khoảng trắng / dòng mới / nhận xét, nó trở thành 4867 byte):

import ceylon.language.meta.model {
    N=Function
}
import ceylon.collection {
    H=HashMap
}
//↑ Import of stuff we need – with a shorter alias.
// (The comment is down here due to a bug in my comment and space
//  remover – it doesn't remove a comment if it is the first token
//  at all.)

// Our data items are either functions or blanks.
interface D of F | b {}

// There is no point in having many blanks – so here a singleton.
object b satisfies D {}

// The function class. Our functions take a number of data items,
// and return a number of data items.
// We know the arity a, and have also an actual function f, and a number
// or already collected arguments.
class F(shared Integer a, [D+](D+) f, [D*] c = [])
        satisfies D {
    // apply once (= collect one parameter). Returns either the result,
    // or a function with arity one less.
    shared [D+] o(D i) {
        [D+] s = [i].prepend(c);
        return a == 1 then f(*s) else [F(a - 1, f, s)];
    }
    // apply fully (= with all needed parameters).
    // The input size should equal the arity.
    shared [D+] y([D+] i) {
        // merge collected and input arguments.
        return f(*i.prepend(c));
    }
}
// creates a shift function from a ceylon function,
// deriving the arity using reflection.
F m<A>(N<[D+],A> f)
        given A satisfies [D+]
        => F(f.parameterTypes.size, (D+ i) => f.apply(*i));

//
// clone: a unary function that duplicates its input: any value x is mapped to [x,x].
//
[D, D] e(D x) => [x, x];

//
// shift: a unary function that takes in an n-ary function f, and returns an
// (n+1)-ary function g that ignores its first argument x, calls f on the
// remaining ones, and tacks x in front of the result. For example,
// shift(clone) is a binary function that takes inputs a,b and returns [a,b,b].
//
[F] t(F f) {
    [D+] g(D+ i) {
        assert (is [D+] r = i.rest);
        return [i[0], *f.y(r)];
    }
    return [F(f.a + 1, g)];
}

//
// fork: a ternary function that takes three inputs a,d,c, and returns [d] if a is a blank,
// and [c] otherwise.
//
[D] k(D a, D d, D c) => a == b then [d] else [c];

//
// call: a binary function that pops a function f and a value x,
//        and applies f to x exactly as ! does.
//
[D+] l(F a, D x) => a.o(x);

//
// chain:  a binary function that pops two functions f and g, and returns their composition:
//         a function h that has the same arity as f, and which takes its inputs normally, applies
//         f to them, and then fully applies g to the result (calls it as many times as its arity
//         dictates), with unused items from the output of f remaining in the result of h. For
//         example, suppose that f is a binary function that clones its second argument, and
//         g is call. If the stack contains [f,g,a,b,c] and we do .!!, then it contains
//         [chain(f,g),a,b,c]; if we do !! next, then f is first applied to a,b, producing
//         [a,b,b], then g is applied to the first two elements of that since its arity is 2,
//         producing [a(b),b], and the stack will finally be [a(b),b,c].
//
[F] n(F f, F g) {
    [D+] h(D+ i) {
        // call f, remember the results.
        [D+] r = f.y(i);
        // first some results from f are the arguments to g:
        assert (is [D+] d = r[0:g.a]);
        // remaining results from f are passed back directly, with the results from g.
        return g.y(d).append(r[g.a...]);
    }
    return [F(f.a, h)];
}

//
// say: a unary function that simply returns its input, and prints 0 if it was a blank,
//      and 1 if it was a function.
// 
[D] y(D x) {
    process.write(x == b then "0" else "1");
    return [x];
}

//
// Interpreter class, which manages the stack and interprets the commands.
// Just call the r method with the individual command characters.
//
class I() {
    // The stack. The only variable in the whole program.
    variable D[] s = [];

    // a hash map of items to be pushed by commands, most build using the m function.
    // The apply command is not here, this is handled separately by the interpreter. 
    value c = H {
        '?'->b,
        '+'->m(`e`),
        '>'->m(`t`),
        '/'->m(`k`),
        '$'->m(`l`),
        '.'->m(`n`),
        '@'->m(`y`)
    };

    // Interprets one command, indicated by a character.
    // Will throw an AssertionError for unknown commands.
    shared void r(Character i) {
        if (i == '!') {
            assert (
                is F f = s[0],
                is D x = s[1]);
            // apply f on x, push the result onto a shortened version of the stack.
            s = f.o(x).append(s[2...]);
        } else {
            assert (is D d = c[i]);
            // push d on top of the stack.
            s = [d].append(s);
        }
    }
}

shared void z() {
    process.readLine()?.collect(I().r);
}

Các chức năng nhân bản e, thay đổi t, ngã ba k, gọi l, nói yvà chuỗi nsử dụng chữ cái cuối cùng của tên cho phiên bản rút gọn, vì điều đó đã cho ít va chạm hơn. (Câu đố: ngã ba ban đầu được định nghĩa theo cách này: [Data] fork(Data a, Data b, Data c) => a == blank then [b] else [c];- khi tôi đổi tên blankđể b, điều này đã phá vỡ, bởi vì nó bây giờ so với các thông số abthay vào đó avới trống Đã cho tôi một thời gian để gỡ lỗi..)

Các zchức năng được chia sẻ bởi vì IDE của tôi chạy những chức năng - công cụ dòng lệnh cũng có thể chạy những phi chia sẻ.


Các phiên bản lặp thực sự sẽ ném StackOverflowError tại một số điểm, sau đó hoàn thành. JVM không có tối ưu hóa ngăn xếp đệ quy (hoặc ít nhất là không có cái nào hoạt động cho chương trình của tôi).
Paŭlo Ebermann
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.