Làm thế nào để chơi golf với đệ quy
Đệ quy, mặc dù không phải là lựa chọn nhanh nhất, nhưng thường là ngắn nhất. Nói chung, đệ quy là ngắn nhất nếu giải pháp có thể đơn giản hóa giải pháp thành một phần nhỏ hơn của thách thức, đặc biệt nếu đầu vào là một số hoặc một chuỗi. Ví dụ, nếu f("abcd")
có thể được tính từ "a"
và f("bcd")
, tốt nhất nên sử dụng đệ quy.
Lấy ví dụ, giai thừa:
n=>[...Array(n).keys()].reduce((x,y)=>x*++y,1)
n=>[...Array(n)].reduce((x,_,i)=>x*++i,1)
n=>[...Array(n)].reduce(x=>x*n--,1)
n=>{for(t=1;n;)t*=n--;return t}
n=>eval("for(t=1;n;)t*=n--")
f=n=>n?n*f(n-1):1
Trong ví dụ này, đệ quy rõ ràng là ngắn hơn bất kỳ tùy chọn nào khác.
Làm thế nào về tổng số mã hóa:
s=>[...s].map(x=>t+=x.charCodeAt(),t=0)|t
s=>[...s].reduce((t,x)=>t+x.charCodeAt())
s=>[for(x of(t=0,s))t+=x.charCodeAt()]|t // Firefox 30+ only
f=s=>s?s.charCodeAt()+f(s.slice(1)):0
Điều này là khó khăn hơn, nhưng chúng ta có thể thấy rằng khi được thực hiện chính xác, đệ quy sẽ tiết kiệm được 4 byte .map
.
Bây giờ chúng ta hãy xem các loại đệ quy khác nhau:
Tiền đệ quy
Đây thường là loại đệ quy ngắn nhất. Các đầu vào được chia thành hai phần a
và b
, và các chức năng tính toán cái gì đó với a
và f(b)
. Quay trở lại ví dụ giai thừa của chúng tôi:
f=n=>n?n*f(n-1):1
Trong trường hợp này, a
là n , b
là n-1 và giá trị được trả về là a*f(b)
.
Lưu ý quan trọng: Tất cả các hàm đệ quy phải có cách dừng đệ quy khi đầu vào đủ nhỏ. Trong chức năng giai thừa, điều này được điều khiển bằng n? :1
, tức là nếu đầu vào bằng 0 , trả về 1 mà không gọi f
lại.
Hậu đệ quy
Hậu đệ tương tự như đệ quy trước, nhưng hơi khác. Đầu vào được chia thành hai phần a
và b
, và hàm tính toán một cái gì đó với a
, sau đó gọi f(b,a)
. Đối số thứ hai thường có giá trị mặc định (nghĩa làf(a,b=1)
).
Tiền đệ quy là tốt khi bạn cần làm một cái gì đó đặc biệt với kết quả cuối cùng. Ví dụ: nếu bạn muốn giai thừa của một số cộng 1:
f=(n,p=1)=>n?f(n-1,n*p):p+1
Tuy nhiên, ngay cả sau đó, không phải lúc nào cũng ngắn hơn sử dụng đệ quy trước trong một chức năng khác:
n=>(f=n=>n?n*f(n-1):1)(n)+1
Vậy khi nào nó ngắn hơn? Bạn có thể nhận thấy rằng đệ quy sau trong ví dụ này yêu cầu dấu ngoặc đơn xung quanh các đối số hàm, trong khi đệ quy trước thì không. Nói chung, nếu cả hai giải pháp đều cần dấu ngoặc đơn xung quanh các đối số, thì đệ quy sau ngắn hơn khoảng 2 byte:
n=>!(g=([x,...a])=>a[0]?x-a.pop()+g(a):0)(n)
f=([x,...a],n=0)=>a[0]?f(a,x-a.pop()+n):!n
(các chương trình ở đây được lấy từ câu trả lời này )
Làm thế nào để tìm ra giải pháp ngắn nhất
Thông thường cách duy nhất để tìm ra phương pháp ngắn nhất là thử tất cả chúng. Điêu nay bao gôm:
- Vòng lặp
.map
(đối với các chuỗi, một trong hai [...s].map
hoặc s.replace
, vì con số, bạn có thể tạo ra một phạm vi )
- Hiểu mảng
- Tiền đệ quy (đôi khi trong các tùy chọn khác)
- Hậu đệ quy
Và đây chỉ là những giải pháp phổ biến nhất; giải pháp tốt nhất có thể là sự kết hợp của những điều này, hoặc thậm chí là một cái gì đó hoàn toàn khác . Cách tốt nhất để tìm ra giải pháp ngắn nhất là thử mọi thứ .