GNU Prolog, 98 byte
b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.
c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).
Câu trả lời này là một ví dụ tuyệt vời về cách Prolog có thể đấu tranh với các định dạng I / O đơn giản nhất. Nó hoạt động theo phong cách Prolog thực sự thông qua việc mô tả vấn đề, thay vì thuật toán để giải quyết nó: nó chỉ định những gì được tính là sắp xếp bong bóng hợp pháp, yêu cầu Prolog tạo ra tất cả các sắp xếp bong bóng đó, sau đó đếm chúng. Thế hệ có 55 ký tự (hai dòng đầu tiên của chương trình). Việc đếm và I / O lấy 43 khác (dòng thứ ba và dòng mới tách hai phần). Tôi cá rằng đây không phải là vấn đề mà OP dự kiến sẽ khiến các ngôn ngữ phải vật lộn với I / O! (Lưu ý: Đánh dấu cú pháp của Stack Exchange làm cho việc này khó đọc hơn, không dễ hơn, vì vậy tôi đã tắt nó).
Giải trình
Hãy bắt đầu với phiên bản mã giả của một chương trình tương tự không thực sự hoạt động:
b(Bubbles,Count) if map(b,Bubbles,BubbleCounts)
and sum(BubbleCounts,InteriorCount)
and Count is InteriorCount + 1
and is_sorted(Bubbles).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
and length(List,NPossibilities).
Một điều khá rõ ràng là cách thức b
hoạt động: chúng tôi biểu diễn các bong bóng thông qua các danh sách được sắp xếp (đó là một cách thực hiện đơn giản của nhiều trang khiến các đa số bằng nhau để so sánh bằng nhau) và một bong bóng duy nhất []
có số lượng là 1, với một bong bóng lớn hơn có số đếm bằng tổng số bong bóng bên trong cộng với 1. Với số lượng 4, chương trình này sẽ (nếu nó hoạt động) tạo ra các danh sách sau:
[[],[],[],[]]
[[],[],[[]]]
[[],[[],[]]]
[[],[[[]]]]
[[[]],[[]]]
[[[],[],[]]]
[[[],[[]]]]
[[[[],[]]]]
[[[[[]]]]]
Chương trình này không phù hợp như một câu trả lời vì nhiều lý do, nhưng điều cấp bách nhất là Prolog không thực sự có một map
vị ngữ (và viết nó sẽ mất quá nhiều byte). Vì vậy, thay vào đó, chúng tôi viết chương trình như thế này:
b([], 0).
b([Head|Tail],Count) if b(Head,HeadCount)
and b(Tail,TailCount)
and Count is HeadCount + TailCount + 1
and is_sorted([Head|Tail]).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
and length(List,NPossibilities).
Vấn đề lớn khác ở đây là nó sẽ đi vào một vòng lặp vô hạn khi chạy, vì cách thức hoạt động của trật tự đánh giá của Prolog. Tuy nhiên, chúng ta có thể giải quyết vòng lặp vô hạn bằng cách sắp xếp lại chương trình một chút:
b([], 0).
b([Head|Tail],Count) if Count #= HeadCount + TailCount + 1
and b(Head,HeadCount)
and b(Tail,TailCount)
and is_sorted([Head|Tail]).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
and length(List,NPossibilities).
Đây có thể trông khá lạ - chúng tôi đang thêm cùng nhau đếm trước khi chúng ta biết chúng là gì - nhưng GNU Prolog của #=
là khả năng xử lý mà loại số học noncausal, và bởi vì đó là dòng đầu tiên của b
, và HeadCount
và TailCount
phải cả hai được ít hơn Count
(được biết đến), nó phục vụ như một phương pháp giới hạn tự nhiên bao nhiêu lần thuật ngữ đệ quy có thể khớp, và do đó làm cho chương trình luôn luôn chấm dứt.
Bước tiếp theo là đánh golf này xuống một chút. Xóa khoảng trắng, sử dụng tên biến ký tự đơn, sử dụng các chữ viết tắt như :-
for if
và ,
for and
, bằng cách sử dụng setof
chứ không phải listof
(nó có tên ngắn hơn và tạo ra kết quả tương tự trong trường hợp này) và sử dụng sort0(X,X)
chứ không phải is_sorted(X)
(vì is_sorted
thực sự không phải là hàm thực, Tôi làm ra nó):
b([],0).
b([H|T],N):-N#=A+B+1,b(H,A),b(T,B),sort0([H|T],[H|T]).
c(X,Y):-setof(A,b(A,X),L),length(L,Y).
Điều này khá ngắn, nhưng nó có thể làm tốt hơn. Cái nhìn sâu sắc quan trọng [H|T]
là thực sự dài dòng như cú pháp danh sách đi. Như các lập trình viên Lisp sẽ biết, một danh sách về cơ bản chỉ được tạo từ các ô khuyết điểm, về cơ bản chỉ là các bộ dữ liệu và hầu như không có phần nào của chương trình này sử dụng các danh sách dựng sẵn. Prolog có một số cú pháp tuple rất ngắn (yêu thích của tôi là A-B
, nhưng yêu thích thứ hai của tôi là A/B
, tôi đang sử dụng ở đây vì nó tạo ra đầu ra gỡ lỗi dễ đọc hơn trong trường hợp này); và chúng ta cũng có thể chọn một ký tự nil
riêng cho cuối danh sách, thay vì bị mắc kẹt với hai ký tự []
(tôi đã chọn x
, nhưng về cơ bản mọi thứ đều hoạt động). Vì vậy, thay vì [H|T]
, chúng ta có thể sử dụng T/H
và nhận đầu ra từb
trông giống như thế này (lưu ý rằng thứ tự sắp xếp trên các bộ dữ liệu hơi khác so với thứ tự trong danh sách, vì vậy chúng không theo thứ tự như trên):
x/x/x/x/x
x/x/x/(x/x)
x/(x/x)/(x/x)
x/x/(x/x/x)
x/(x/x/x/x)
x/x/(x/(x/x))
x/(x/x/(x/x))
x/(x/(x/x/x))
x/(x/(x/(x/x)))
Điều này khá khó đọc hơn các danh sách lồng nhau ở trên, nhưng nó có thể; bỏ qua tinh thần x
và giải thích /()
như một bong bóng (hoặc chỉ đơn giản /
là một bong bóng thoái hóa không có nội dung, nếu không có()
nó sau đó) và các yếu tố có sự tương ứng 1-1 (nếu bị rối loạn) với phiên bản danh sách được hiển thị ở trên .
Tất nhiên, đại diện danh sách này, mặc dù ngắn hơn nhiều, có một nhược điểm lớn; nó không được tích hợp vào ngôn ngữ, vì vậy chúng tôi không thể sử dụng sort0
để kiểm tra xem danh sách của chúng tôi có được sắp xếp hay không. sort0
Tuy nhiên, khá dài dòng, do đó, việc thực hiện bằng tay không phải là một mất mát lớn (trên thực tế, thực hiện bằng tay trên [H|T]
biểu diễn danh sách có cùng số byte). Cái nhìn sâu sắc quan trọng ở đây là chương trình dưới dạng văn bản kiểm tra xem danh sách đã được sắp xếp chưa, nếu đuôi của nó được sắp xếp, nếu đuôi của nó được sắp xếp, v.v. có rất nhiều kiểm tra dư thừa, và chúng ta có thể khai thác điều đó. Thay vào đó, chúng tôi sẽ kiểm tra để đảm bảo rằng hai yếu tố đầu tiên được xếp theo thứ tự (điều này đảm bảo rằng danh sách sẽ kết thúc được sắp xếp một khi danh sách đó và tất cả các hậu tố của nó được kiểm tra).
Yếu tố đầu tiên có thể dễ dàng truy cập; đó chỉ là người đứng đầu danh sách H
. Tuy nhiên, phần tử thứ hai khá khó truy cập và có thể không tồn tại. May mắn thay, x
ít hơn tất cả các bộ dữ liệu mà chúng tôi đang xem xét (thông qua toán tử so sánh tổng quát của Prolog @>=
), vì vậy chúng tôi có thể xem xét "yếu tố thứ hai" của danh sách đơn x
và chương trình sẽ hoạt động tốt. Đối với việc thực sự truy cập vào phần tử thứ hai, phương thức khó nhất là thêm một đối số thứ ba (một đối số out) vào b
, trả về x
trong trường hợp cơ sở và H
trong trường hợp đệ quy; điều này có nghĩa là chúng ta có thể lấy phần đầu của đuôi như một đầu ra từ lệnh gọi đệ quy thứ hai đến B
, và tất nhiên phần đầu của phần đuôi là phần tử thứ hai của danh sách. Vì vậy, b
trông như thế này bây giờ:
b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.
Trường hợp cơ sở là đủ đơn giản (danh sách trống, trả về số 0, "phần tử đầu tiên" của danh sách trống là x
). Trường hợp đệ quy bắt đầu giống như trước đây (chỉ với T/H
ký hiệu chứ không phải [H|T]
, và H
như là một đối số phụ); chúng tôi bỏ qua đối số phụ từ cuộc gọi đệ quy trên đầu, nhưng lưu trữ nó trong J
cuộc gọi đệ quy ở đuôi. Sau đó, tất cả những gì chúng ta phải làm là đảm bảo H
lớn hơn hoặc bằng J
(nghĩa là "nếu danh sách có ít nhất hai phần tử, phần tử đầu tiên lớn hơn hoặc bằng phần thứ hai) để đảm bảo rằng danh sách kết thúc được sắp xếp.
Thật không may, setof
sẽ phù hợp nếu chúng ta cố gắng sử dụng định nghĩa trước đó c
cùng với định nghĩa mới này b
, bởi vì nó xử lý các tham số không được sử dụng theo cách tương tự như SQL GROUP BY
, hoàn toàn không phải là điều chúng ta muốn. Có thể cấu hình lại nó để làm những gì chúng ta muốn, nhưng việc cấu hình lại đó có chi phí cho các ký tự. Thay vào đó, chúng tôi sử dụng findall
, có hành vi mặc định thuận tiện hơn và chỉ dài hơn hai ký tự, cho chúng tôi định nghĩa về c
:
c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).
Và đó là chương trình hoàn chỉnh; Tạo ra các mẫu bong bóng một cách chặt chẽ, sau đó dành toàn bộ tải byte để đếm chúng (chúng ta cần một khoảng thời gian khá dài findall
để chuyển đổi trình tạo thành danh sách, sau đó không may được đặt tên rõ ràng length
để kiểm tra độ dài của danh sách đó, cộng với bảng kê khai để khai báo hàm).