Một giải pháp chức năng thuần túy cho vấn đề này có thể sạch như mệnh lệnh không?


10

Tôi có một bài tập về Python như sau:

  • một đa thức được đưa ra dưới dạng một bộ các hệ số sao cho các lũy thừa được xác định bởi các chỉ số, ví dụ: (9,7,5) có nghĩa là 9 + 7 * x + 5 * x ^ 2

  • viết hàm để tính giá trị của nó cho x đã cho

Vì tôi tham gia lập trình chức năng gần đây, tôi đã viết

def evaluate1(poly, x):
  coeff = 0
  power = 1
  return reduce(lambda accu,pair : accu + pair[coeff] * x**pair[power],
                map(lambda x,y:(x,y), poly, range(len(poly))),
                0)

mà tôi cho là không thể đọc được, vì vậy tôi đã viết

def evaluate2(poly, x):
  power = 0
  result = 1
  return reduce(lambda accu,coeff : (accu[power]+1, accu[result] + coeff * x**accu[power]),
                poly,
                (0,0)
               )[result]

mà ít nhất là không thể đọc được, vì vậy tôi đã viết

def evaluate3(poly, x):
  return poly[0]+x*evaluate(poly[1:],x) if len(poly)>0 else 0

Điều này có thể kém hiệu quả hơn (chỉnh sửa: Tôi đã sai!) vì nó sử dụng nhiều phép nhân thay vì lũy thừa, về nguyên tắc, tôi không quan tâm đến các phép đo ở đây (chỉnh sửa: Tôi thật ngớ ngẩn! vẫn không thể đọc được (có thể tranh luận) như giải pháp lặp:

def evaluate4(poly, x):
  result = 0
  for i in range(0,len(poly)):
      result += poly[i] * x**i
  return result

Có một giải pháp chức năng thuần túy có thể đọc được như là bắt buộc và gần với nó trong hiệu quả?

Phải thừa nhận rằng, một sự thay đổi đại diện sẽ giúp ích, nhưng điều này được đưa ra bởi bài tập.

Có thể là Haskell hoặc Lisp, không chỉ Python.


7
Theo kinh nghiệm của tôi, mã chức năng hoàn toàn theo nghĩa không sử dụng các biến có thể thay đổi (cũng có nghĩa là không sử dụng forcác vòng lặp chẳng hạn) là một mục tiêu xấu để nhắm đến trong Python. Liên kết lại các biến một cách thận trọng và không làm biến đổi các đối tượng mang lại cho bạn gần như tất cả các lợi ích và làm cho mã dễ đọc hơn nhiều. Vì các đối tượng số là bất biến và nó chỉ phản hồi hai tên cục bộ, nên giải pháp "bắt buộc" của bạn nhận ra các đức tính lập trình chức năng tốt hơn bất kỳ mã Python "hoàn toàn thuần túy" nào.

2
BTW Phương pháp nhân là phương pháp của Horner và nó hiệu quả hơn so với lũy thừa ở mỗi bước, vì phép lũy thừa đòi hỏi các phép nhân rất giống nhau và sau đó là một số nhân nữa.

1
Python nổi tiếng là xấu xí khi bạn sử dụng lambda, so với các ngôn ngữ có chức năng cú pháp ẩn danh nhẹ hơn. Một phần trong đó có thể góp phần vào sự xuất hiện "ô uế".
KChaloux

@KChaloux đó chính xác là những gì tôi sẽ nói. Hỗ trợ lập trình chức năng là một phần của suy nghĩ trong Python ở nhiều khía cạnh và nó là loại chương trình. Mặc dù vậy, tôi không nghĩ ngay cả phiên bản đầu tiên cũng khó đọc đến mức bạn không thể hiểu chuyện gì đang xảy ra.
Evicatos

Tôi thực sự bối rối bởi mã của bạn, trong khi phạm vi vấn đề có một phương trình toán học cực kỳ rõ ràng, tại sao bạn không sử dụng phương trình toán học đó nguyên văn? Nó khá dễ dàng biến thành một hàm được cung cấp bất kỳ ngôn ngữ nào ... không chắc bạn muốn ánh xạ hoặc giảm hoặc lặp lại bất cứ điều gì khi câu hỏi đang yêu cầu một hàm đánh giá một phương trình và đưa ra phương trình đó - nó không yêu cầu lặp đi lặp lại tất cả ...
Jimmy Hoffa

Câu trả lời:


13

Phương pháp của Horner có lẽ hiệu quả hơn về mặt tính toán như @delnan chỉ ra, nhưng tôi sẽ gọi điều này khá dễ đọc trong Python cho giải pháp lũy thừa:

def eval_poly(poly, x):
    return sum( [a * x**i for i,a in enumerate(poly)] )

17
Bỏ dấu ngoặc vuông và đặt cho các biến nhiều tên mô tả hơn và thậm chí còn tốt hơn: sum(coeff * X**power for power, coeff in enumerate(poly))
Izkata

1
Tôi rất buồn vì các câu trả lời được đăng khác rất phức tạp. Sử dụng ngôn ngữ để lợi thế của bạn!
Izkata

hiểu giống như một "vòng lậu" trong lập trình chức năng
user1353 21/12/13

7
@ user1353 Không, đó là đường cú pháp cho thành phần của mapfilter. Người ta cũng có thể nghĩ về nó như một vòng lặp for của một hình dạng cụ thể, nhưng các vòng lặp của hình dạng đó tương đương với tổ hợp funcitonal đã nói ở trên.

7

Nhiều ngôn ngữ chức năng có triển khai mapi cho phép bạn có một chỉ mục được dệt qua bản đồ. Kết hợp điều đó với một khoản tiền và bạn có những điều sau trong F #:

let compute coefficients x = 
    coefficients 
        |> Seq.mapi (fun i c -> c * Math.Pow(x, (float)i))
        |> Seq.sum

2
Và ngay cả khi họ không, miễn là bạn hiểu cách thức maphoạt động, việc viết một trong những thứ của bạn là khá đơn giản.
KChaloux

4

Tôi không hiểu làm thế nào mã của bạn liên quan đến phạm vi vấn đề bạn đã xác định, vì vậy tôi sẽ đưa ra phiên bản của tôi về những gì mã của bạn bỏ qua phạm vi vấn đề (dựa trên mã bắt buộc bạn đã viết).

Haskell khá dễ đọc (cách tiếp cận này có thể dễ dàng dịch sang bất kỳ ngôn ngữ FP nào có danh sách phá hủy và xuất hiện thuần túy và dễ đọc):

eval acc exp val [] = acc
eval acc exp val (x:xs) = eval (acc + execPoly) (exp+1) xs
  where execPoly = x * (val^exp)

Đôi khi cách tiếp cận đơn giản ngây thơ trong haskell như thế lại sạch sẽ hơn cách tiếp cận ngắn gọn hơn đối với những người ít quen với FP.

Một cách tiếp cận rõ ràng hơn mà vẫn hoàn toàn thuần túy là:

steval val poly = runST $ do
  accAndExp <- newSTRef (0,1)
  forM_ poly $ \x -> do
    modifySTRef accAndExp (updateAccAndExp x)
  readSTRef accAndExp
  where updateAccAndExp x (acc, exp) = (acc + x*(val^exp), exp + 1)

Phần thưởng cho cách tiếp cận thứ hai là trong đơn vị ST, nó sẽ hoạt động rất tốt.

Mặc dù chắc chắn, việc triển khai thực sự có khả năng nhất từ ​​Haskeller sẽ là zipwith được đề cập trong một câu trả lời khác ở trên. zipWithlà một cách tiếp cận rất điển hình và tôi tin rằng Python có thể bắt chước cách tiếp cận nén của việc kết hợp các hàm và một bộ chỉ mục có thể được ánh xạ.


4

Nếu bạn chỉ có một tuple (cố định), tại sao không làm điều này (trong Haskell):

evalPolyTuple (c, b, a) x = c + b*x + a*x^2

Nếu thay vào đó bạn có một danh sách các hệ số, bạn có thể sử dụng:

evalPolyList coefs x = sum $ zipWith (\c p -> c*x^p) coefs [0..]

hoặc với mức giảm như bạn đã có:

evalPolyList' coefs x = foldl' (\sum (c, p) -> sum + c*x^p) 0 $ zip coefs [0..]

1
Nó KHÔNG phải là bài tập về nhà! Chưa kể tôi đã làm 3 giải pháp rồi.
user1353 20/12/13

Một nửa thời gian trong Python (bao gồm cả trong trường hợp này), "tuple" có nghĩa là "danh sách bất biến" và do đó có độ dài tùy ý.

rõ ràng là độ dài tùy ý
user1353 20/12/13

1
không phải vì trăn, mà vì đa thức hàm ý độ dài tùy ý và kích thước cố định sẽ không phải là một bài tập lớn
user1353 20/12/13

1
@delnan Thật thú vị. Tôi luôn luôn tuplecó nghĩa là một tập hợp các giá trị có kích thước cố định, mỗi loại có khả năng khác nhau, không thể thêm hoặc xóa khỏi. Tôi không bao giờ thực sự hiểu tại sao một ngôn ngữ động với các danh sách, chấp nhận đầu vào không đồng nhất, sẽ cần chúng.
KChaloux

3

Có một bộ các bước chung mà bạn có thể sử dụng để cải thiện khả năng đọc của các thuật toán chức năng:

  • Đặt tên trên kết quả trung gian của bạn, thay vì cố gắng nhồi nhét mọi thứ trên một dòng.
  • Sử dụng các hàm được đặt tên thay vì lambdas, đặc biệt là trong các ngôn ngữ có cú pháp lambda dài dòng. Thật dễ dàng để đọc một cái gì đó giống như evaluateTermmột biểu thức lambda dài. Chỉ vì bạn có thể sử dụng lambda không nhất thiết là bạn nên .
  • Nếu một trong những chức năng được đặt tên hiện tại của bạn trông giống như một cái gì đó sẽ xuất hiện khá thường xuyên, thì rất có thể nó đã có trong thư viện chuẩn. Nhìn xung quanh. Con trăn của tôi hơi rỉ sét, nhưng có vẻ như bạn đã phát minh lại enumeratehoặc zipWith.
  • Thông thường, việc nhìn thấy các chức năng và kết quả trung gian được đặt tên sẽ giúp dễ dàng suy luận về những gì đang diễn ra và đơn giản hóa nó, tại thời điểm đó có thể có ý nghĩa khi đặt lambda trở lại hoặc kết hợp một số dòng lại với nhau.
  • Nếu một mệnh lệnh cho vòng lặp có vẻ dễ đọc hơn, nhiều khả năng một sự hiểu biết sẽ hoạt động tốt.
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.