Bu problemin saf işlevsel bir çözümü, zorunluluk kadar temiz olabilir mi?


10

Python'da şöyle bir egzersiz yapıyorum:

  • bir polinom, güçler indeksler tarafından belirlenecek şekilde bir dizi katsayı olarak verilir, örneğin: (9,7,5), 9 + 7 * x + 5 * x ^ 2 anlamına gelir

  • verilen x için değerini hesaplamak için bir fonksiyon yazın

Son zamanlarda fonksiyonel programlamaya başladığım için yazdım

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)

okunamayan sayılırım, bu yüzden yazdım

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]

ki en azından okunamayacak kadar

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

bu daha az verimli olabilir (değiştir: yanlışmışım!) çünkü üs alma yerine birçok çarpma kullanır, prensip olarak, burada ölçümleri umursamıyorum (değiştir: Ne kadar aptalım! Ölçüm yanlış algımı işaret ederdi!) ve yine de yinelemeli çözüm kadar okunabilir (tartışmasız) değil:

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

Zorunlu olduğu kadar okunabilir ve verimlilikte ona yakın, saf fonksiyonel bir çözüm var mı?

Kuşkusuz, bir temsil değişikliği yardımcı olacaktır, ancak bu tatbikat tarafından verildi.

Haskell veya Lisp de olabilir, sadece Python değil.


7
Deneyimlerime göre, değişken değişkenlerin kullanılmaması anlamında tamamen işlevsel kod ( forörneğin döngülerin kullanılmaması anlamına gelir ) Python'da amaçlanan kötü bir hedeftir. Değişkenleri mantıklı bir şekilde yeniden bağlamak ve nesneleri mutasyona uğratmamak size neredeyse tüm avantajları sağlar ve kodu sonsuza kadar daha okunabilir hale getirir. Sayı nesneleri değişmez olduğundan ve yalnızca iki yerel adı yeniden hatırlattığından, "zorunlu" çözümünüz, herhangi bir "kesinlikle saf" Python kodundan daha işlevsel programlama erdemlerini daha iyi gerçekleştirir.

2
BTW Çarpma yöntemi Horner'ın yöntemidir ve her adımda üs alma işleminden daha etkilidir, çünkü üs alma işlemi aynı çarpma işlemlerini ve sonra biraz daha fazlasını gerektirir.

1
Python, lambdadaha açık bir anonim sözdizimi işlevine sahip dillerle karşılaştırıldığında, kullanmaya başladığınızda çok çirkin . Bunun bir kısmı muhtemelen "kirli" görünüme katkıda bulunur.
KChaloux

@KChaloux tam olarak bunu söyleyecektim. Fonksiyonel programlama desteği Python'da pek çok açıdan bir şekilde düşünülmüş ve bir çeşit şov. Buna rağmen, ilk versiyonun bile o kadar korkunç okunamaz olduğunu düşünmüyorum, ne olduğunu anlayamıyorsunuz.
Evicatos

Kodunuzun gerçekten kafam karıştı, problem-kapsamının son derece açık olan bir matematiksel denklemi olmasına rağmen, neden sadece bu matematik denklemini kelimesi kelimesine kullanmıyorsunuz? Herhangi bir dil verilen bir fonksiyona oldukça kolay bir şekilde dönüşür ... soru tek bir denklemi değerlendiren ve bu denklemi veren bir fonksiyon istediğinde neyi eşlemek veya azaltmak veya yinelemek istediğinizden emin değildir - istemez hiç yineleme ...
Jimmy Hoffa

Yanıtlar:


13

@ Delnan'ın işaret ettiği gibi Horner'ın yöntemi muhtemelen daha hesaplamalı olarak etkilidir, ancak bu üslü çözüm için Python'da oldukça okunabilir diyebilirim:

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

17
Köşeli parantezleri bırakın ve değişkenlere daha açıklayıcı isimler verin ve daha da iyi: sum(coeff * X**power for power, coeff in enumerate(poly))
Izkata

1
Gönderilen diğer cevapların çok karmaşık olması beni üzüyor. Dili kendi yararınıza kullanın!
Izkata

anlama fonksiyonel programlama içine bir "döngü kaçak" gibidir
user1358

7
@ user1358 Hayır, bileşimi için sözdizimsel şeker var mapve filter. Ayrıca, belirli bir şeklin bir döngüsü için de düşünülebilir, ancak bu şeklin halkaları yukarıda bahsedilen funcitonal birleştiriciye eşdeğerdir.

7

Birçok işlevsel dilde, bir harita üzerinden bir dizin oluşturmanıza olanak sağlayan mapi uygulamaları vardır. Bunu bir toplamla birleştirin ve F # içinde aşağıdakilere sahip olun:

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

2
Ve yapmasalar bile, nasıl mapçalıştığını anladığınız sürece, kendinizden birini yazmak oldukça basit olmalıdır.
KChaloux

4

Kodunuzun tanımladığınız sorun kapsamıyla nasıl ilişkili olduğunu anlamıyorum, bu yüzden kodunuzun sorun kapsamını görmezden geldiği şeyin versiyonunu vereceğim (yazdığınız zorunlu koda göre).

Oldukça okunabilir haskell (bu yaklaşım, liste yıkımına sahip olan ve saf ve okunabilir hale gelen herhangi bir FP diline kolayca çevrilebilir):

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

Bazen haskell'deki saf basit yaklaşım, FP'ye daha az alışkın olan insanlara daha kısa yaklaşımdan daha temizdir.

Hala tamamen saf olan daha açıkça zorunlu bir yaklaşım:

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)

İkinci yaklaşıma bonus çok iyi performans gösterecek ST monad'da olmaktır.

Emin olmakla birlikte, bir Haskeller'den en olası gerçek uygulama, yukarıdaki başka bir cevapta belirtilen zipwith olacaktır. zipWithçok tipik bir yaklaşımdır ve Python'un fonksiyonları ve haritalanabilir bir indeksleyiciyi birleştirmenin sıkıştırma yaklaşımını taklit edebileceğine inanıyorum.


4

Eğer varsa sadece bir (sabit) tuple var, neden (Haskell) bunu:

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

Bunun yerine bir katsayılar listeniz varsa, şunları kullanabilirsiniz:

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

veya sahip olduğunuz gibi bir azalma ile:

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

1
Ödev değil! Şimdiden 3 çözüm yaptığımı söylemiyorum.
user1358

Python'daki zamanın yarısı (bu örnek dahil), "tuple" "değişmez liste" anlamına gelir ve dolayısıyla keyfi uzunluktadır.

bariz şekilde keyfi uzunluk
user1358

1
python yüzünden değil, polinom keyfi uzunluk anlamına
geldiğinden

1
@delnan Bu ilginç. Her zaman tupleeklenemeyen veya kaldırılamayan, her biri potansiyel olarak farklı türlerde olan sabit boyutlu bir değer kümesi anlamına geldim . Heterojen girdileri kabul eden listelere sahip dinamik bir dilin neden bunlara ihtiyacı olacağını hiç anlamadım.
KChaloux

3

İşlevsel algoritmaların okunabilirliğini artırmak için kullanabileceğiniz genel bir dizi adım vardır:

  • Her şeyi tek bir satırda sıkıştırmaya çalışmak yerine ara sonuçlarınıza adlar koyun.
  • Lambda yerine adlandırılmış işlevleri, özellikle ayrıntılı lambda sözdizimi olan dillerde kullanın. evaluateTermUzun bir lambda ifadesinden çok bir şey okumak çok daha kolay . Sadece çünkü edebilirsiniz mutlaka anlamına gelmez lambda kullanmak gerekir .
  • Şimdi adlandırılmış işlevlerinizden biri oldukça sık ortaya çıkan bir şeye benziyorsa, zaten standart kütüphanede olma olasılığı vardır. Etrafa bak. Benim piton biraz paslı, ancak temelde yeniden keşfetti gibi görünüyor enumerateya zipWith.
  • Genellikle, işlevlerin ve ara sonuçların adlandırılması, neler olup bittiğini düşünmeyi ve basitleştirmeyi kolaylaştırır, bu noktada bir lambda'yı geri koymak veya bazı satırları tekrar birleştirmek mantıklı olabilir.
  • Döngü için bir zorunluluk daha okunabilir görünüyorsa, bir kavrama şansı iyi çalışır.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.