Bisakah solusi murni-fungsional untuk masalah ini menjadi sebersih imperatif?

10

Saya memiliki latihan dengan Python sebagai berikut:

  • polinomial diberikan sebagai tupel koefisien sehingga kekuatan ditentukan oleh indeks, misalnya: (9,7,5) berarti 9 + 7 * x + 5 * x ^ 2

  • tulis fungsi untuk menghitung nilainya untuk diberikan x

Karena saya ke pemrograman fungsional belakangan ini, saya menulis

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)

yang saya anggap tidak terbaca, jadi saya menulis

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]

yang setidaknya sama tidak terbaca, jadi saya menulis

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

yang mungkin kurang efisien (sunting: saya salah!) karena menggunakan banyak perkalian alih-alih eksponensial, pada prinsipnya, saya tidak peduli tentang pengukuran di sini (sunting: Betapa bodohnya saya! Mengukur akan menunjukkan kesalahpahaman saya!) dan masih belum terbaca (bisa dibilang) sebagai solusi berulang:

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

Apakah ada solusi murni fungsional yang dapat dibaca seperti keharusan dan mendekati efisiensi?

Memang, perubahan representasi akan membantu, tetapi ini diberikan oleh latihan.

Bisa juga Haskell atau Lisp, bukan hanya Python.

pengguna1358
sumber
7
Dalam pengalaman saya, kode murni fungsional dalam arti tidak menggunakan variabel yang bisa berubah-ubah (yang juga menyiratkan tidak menggunakan forloop, misalnya) adalah tujuan yang buruk untuk dibidik dengan Python. Mengikat variabel secara bijaksana dan tidak memutasikan objek memberi Anda hampir semua manfaat dan membuat kode jauh lebih mudah dibaca. Karena objek nomor tidak dapat diubah dan hanya mengubah dua nama lokal, solusi "imperatif" Anda lebih menyadari nilai pemrograman fungsional daripada kode Python "murni murni" apa pun.
2
BTW Metode penggandaan adalah metode Horner dan lebih efisien daripada eksponensial pada setiap langkah, karena eksponensial memerlukan penggandaan yang sama dan kemudian beberapa lagi.
1
Python agak terkenal jelek ketika Anda mulai menggunakan lambda, dibandingkan dengan bahasa dengan fungsi sintaksis anonim yang lebih ringan. Sebagian dari itu mungkin berkontribusi pada penampilan "najis".
KChaloux
@Khalhal itulah yang akan saya katakan. Dukungan pemrograman fungsional agak ketinggalan jaman dalam Python dalam banyak hal dan itu semacam pertunjukan. Meski begitu saya tidak berpikir bahkan versi pertama begitu mengerikan terbaca sehingga Anda tidak dapat mengetahui apa yang terjadi.
Evicatos
Saya benar-benar bingung dengan kode Anda, sedangkan lingkup masalah memiliki persamaan matematika yang sangat jelas, mengapa Anda tidak menggunakan persamaan matematika itu kata demi kata saja? Ini cukup mudah berubah menjadi fungsi yang diberikan bahasa apa pun ... tidak yakin apa yang ingin Anda petakan atau kurangi atau ulangi apa pun saat pertanyaannya menanyakan fungsi yang mengevaluasi satu persamaan dan memberikan persamaan itu - ia tidak meminta iterasi sama sekali ...
Jimmy Hoffa

Jawaban:

13

Metode Horner mungkin lebih efisien secara komputasi seperti yang ditunjukkan @delnan, tapi saya akan menyebut ini cukup mudah dibaca dengan Python untuk solusi eksponensial:

def eval_poly(poly, x):
    return sum( [a * x**i for i,a in enumerate(poly)] )
aelfric5578
sumber
17
Jatuhkan tanda kurung siku dan beri nama variabel lebih deskriptif, dan itu bahkan lebih baik: sum(coeff * X**power for power, coeff in enumerate(poly))
Izkata
1
Agak menyedihkan saya bahwa jawaban yang diposting lainnya sangat kompleks. Gunakan bahasa untuk keuntungan Anda!
Izkata
pemahaman seperti for-loop "diselundupkan" ke dalam pemrograman fungsional
user1358
7
@ user1358 Tidak, itu gula sintaksis untuk komposisi mapdan filter. Kita juga dapat menganggapnya sebagai untuk loop dari bentuk tertentu, tetapi loop dari bentuk itu setara dengan kombinator funcitonal yang disebutkan sebelumnya.
7

Banyak bahasa fungsional memiliki implementasi mapi yang memungkinkan Anda memiliki indeks yang dianyam melalui peta. Gabungkan itu dengan jumlah dan Anda memiliki yang berikut di F #:

let compute coefficients x = 
    coefficients 
        |> Seq.mapi (fun i c -> c * Math.Pow(x, (float)i))
        |> Seq.sum
Steven Evers
sumber
2
Dan bahkan jika mereka tidak melakukannya, selama Anda mengerti cara mapkerjanya, itu harus cukup sederhana untuk menulis sendiri.
KChaloux
4

Saya tidak mengerti bagaimana kode Anda berhubungan dengan ruang lingkup masalah yang Anda tentukan, jadi saya akan memberikan versi saya tentang apa yang kode Anda mengabaikan ruang lingkup masalahnya (berdasarkan pada kode imperatif yang Anda tulis).

Haskell yang mudah dibaca (pendekatan ini dapat dengan mudah diterjemahkan ke bahasa FP mana pun yang memiliki daftar yang merusak dan keluar murni dan dapat dibaca):

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

Kadang-kadang pendekatan sederhana naif dalam haskell seperti itu lebih bersih daripada pendekatan yang lebih ringkas untuk orang yang kurang terbiasa dengan FP.

Pendekatan imperatif yang lebih jelas yang masih sepenuhnya murni adalah:

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)

bonus untuk pendekatan kedua sedang di ST monad itu akan berkinerja sangat baik.

Meskipun untuk memastikan, implementasi nyata yang paling mungkin dari Haskeller adalah zipwith yang disebutkan dalam jawaban lain di atas. zipWithadalah pendekatan yang sangat khas dan saya percaya Python dapat meniru pendekatan zipping menggabungkan fungsi dan pengindeks yang dapat dipetakan.

Jimmy Hoffa
sumber
4

Jika Anda hanya memiliki tuple (tetap), mengapa tidak melakukan ini (di Haskell):

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

Jika Anda memiliki daftar koefisien, Anda dapat menggunakan:

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

atau dengan potongan seperti yang Anda miliki:

evalPolyList' coefs x = foldl' (\sum (c, p) -> sum + c*x^p) 0 $ zip coefs [0..]
paul
sumber
1
Ini BUKAN PR! Belum lagi saya sudah melakukan 3 solusi.
user1358
Separuh dari waktu dalam Python (termasuk dalam kasus ini), "tuple" berarti "daftar abadi" dan karenanya panjangnya sewenang-wenang.
panjang jelas sewenang-wenang
user1358
1
bukan karena python, tetapi karena polinomial menyiratkan panjang sewenang-wenang, dan ukuran tetap tidak akan menjadi besar latihan
user1358
1
@delnan Itu menarik. Saya selalu tuplemengartikan serangkaian nilai ukuran tetap, masing-masing dari tipe yang berpotensi berbeda, yang tidak dapat ditambahkan atau dihapus. Saya tidak pernah benar-benar mengerti mengapa bahasa yang dinamis dengan daftar, yang menerima input heterogen, membutuhkannya.
KChaloux
3

Ada serangkaian langkah umum yang dapat Anda gunakan untuk meningkatkan keterbacaan algoritma fungsional:

  • Letakkan nama pada hasil antara Anda, alih-alih mencoba menjejalkan semuanya pada satu baris.
  • Gunakan fungsi bernama alih-alih lambdas, terutama dalam bahasa dengan sintaks verbose lambda. Jauh lebih mudah untuk membaca evaluateTermdaripada ekspresi lambda yang panjang. Hanya karena Anda dapat menggunakan lambda tidak selalu berarti Anda harus melakukannya .
  • Jika salah satu dari fungsi Anda yang sekarang bernama tampak seperti sesuatu yang akan muncul cukup sering, kemungkinan itu sudah ada di perpustakaan standar. Lihatlah sekeliling. Python saya sedikit berkarat, tetapi sepertinya Anda pada dasarnya diciptakan kembali enumerateatau zipWith.
  • Seringkali, melihat fungsi dan hasil antara bernama membuatnya lebih mudah untuk alasan tentang apa yang terjadi dan menyederhanakannya, pada titik itu mungkin masuk akal untuk memasukkan lambda kembali atau menggabungkan beberapa garis kembali bersama.
  • Jika suatu keharusan untuk loop terlihat lebih mudah dibaca, kemungkinan untuk pemahaman akan bekerja dengan baik.
Karl Bielefeldt
sumber