Чи може чисто функціональне рішення цієї проблеми бути таким же чистим, як і імператив?


10

У мене вправа в Python наступна:

  • поліном задається у вигляді множини коефіцієнтів, таким чином, що потужність визначається за показниками, наприклад: (9,7,5) означає 9 + 7 * x + 5 * x ^ 2

  • написати функцію для обчислення її значення для заданого x

Оскільки я останнім часом займаюся функціональним програмуванням, я писав

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)

що я вважаю нечитабельним, тому написав

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]

що принаймні як нечитабельне, так я написав

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

що може бути менш ефективним (редагувати: я помилявся!), оскільки він використовує безліч множень замість експоненції, в принципі мені тут не байдуже вимірювання (редагувати: Як глупо! все ще не настільки читабельні (можливо), як ітераційне рішення:

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

Чи є чисте функціональне рішення таке читабельне, як імперативне і близьке до нього за ефективністю?

Щоправда, зміна представництва допоможе, але це було надано вправою.

Це може бути Хаскелл або Лісп, а не лише Пітон.


7
На мій досвід, чисто функціональний код у сенсі невикористання змінних змінних (що також передбачає, наприклад, не використання forциклів) є поганою метою, на яку слід прагнути в Python. Повторне зв’язування змінних з розумним та не мутуючим об'єктом дає вам майже всі переваги та робить код нескінченно більш читабельним. Оскільки об'єкти чисел незмінні і він лише відновлює дві локальні назви, ваше "імперативне" рішення краще реалізує функціональні чесноти програмування, ніж будь-який "суворо чистий" код Python.

2
BTW Метод множення є методом Горнера, і він є більш ефективним, ніж експонентація на кожному кроці, оскільки експоненція вимагає тих самих множень, а потім ще декілька.

1
Python є начебто некрасивим, коли ви користуєтесь ним lambda, порівняно з мовами з легшою функцією анонімного синтаксису. Частина цього, ймовірно, сприяє "нечистому" вигляду.
KChaloux

@KChaloux - це саме те, що я збирався сказати. Підтримка функціонального програмування в багатьох відношеннях є певною думкою в Python, і це свого роду шоу. Навіть тому я не думаю, що навіть перша версія настільки жахливо нечитабельна, що ви не можете зрозуміти, що відбувається.
Евікатос

Я дуже заплутаний у вашому коді, тоді як область задач має математичне рівняння, що є надзвичайно зрозумілим, чому ви просто не використовуєте це математичне рівняння дослівно? Це досить легко перетвориться на функцію, задану будь-якою мовою ... не впевнений, для чого потрібно картувати, зменшувати чи повторювати що-небудь, коли питання запитує функцію, яка оцінює єдине рівняння і дає це рівняння - воно не запитує ітерація взагалі ...
Джиммі Хоффа

Відповіді:


13

Метод Хорнера, ймовірно, більш обчислювально ефективний, як вказує @delnan, але я б назвав це досить читабельним в Python для рішення експоненції:

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

17
Опустіть квадратні дужки і дайте змінні більш описові назви, і це ще краще: sum(coeff * X**power for power, coeff in enumerate(poly))
Izkata

1
Мене це засмучує, що інші розміщені відповіді такі складні. Використовуйте мову на вашу користь!
Ізката

розуміння - це начебто цикл "контрабанди" в функціональне програмування
user1358

7
@ user1358 Ні, це синтаксичний цукор для складу mapта filter. Можна також вважати це петлею певної форми, але петлі такої форми еквівалентні вищезгаданому функціональному комбінатору.

7

У багатьох функціональних мовах є виконання карт, які дозволяють вам індексувати через карту. Поєднайте це із сумою, і у вас є наступне на F #:

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

2
І навіть якщо вони цього не роблять, доки ви розумієте, як це mapпрацює, слід написати досить своє.
KChaloux

4

Я не розумію, як ваш код стосується визначеної вами задачі, тому я наведу свою версію того, що ваш код ігнорує область проблеми (на основі імперативного коду, який ви написали).

Досить читабельний haskell (такий підхід можна легко перекласти на будь-яку мову FP, яка має руйнування списку і виходить чистою та читаною):

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

Іноді наївний простий підхід у подібному хаскелі чистіший, ніж більш стислий підхід до людей, менш звикших до ПП.

Більш чітко необхідний підхід, який все ще є абсолютно чистим:

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)

бонус до другого підходу полягає в тому, що в монаді СТ він буде працювати дуже добре.

Хоча для певності, найбільш вірогідною реальною реалізацією від Haskeller буде zipwith, згаданий в іншій відповіді вище. zipWithце дуже типовий підхід, і я вважаю, що Python може імітувати підхід блискавки комбінування функцій та індексатора, який можна відобразити на карті.


4

Якщо у вас просто є (фіксований) кортеж, чому б не зробити цього (в Haskell):

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

Якщо замість цього у вас є список коефіцієнтів, ви можете використовувати:

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

або зі зменшенням, як у вас було:

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

1
Це НЕ домашнє завдання! Не кажучи вже про те, що я вже зробив 3 рішення.
user1358

Половину часу в Python (включаючи в цьому випадку) "кортеж" означає "незмінний список" і, отже, довільна довжина.

очевидно довільна довжина
user1358

1
не через python, а тому, що многочлен передбачає довільну довжину, а фіксований розмір не буде великим вправою
user1358

1
@delnan Це цікаво. Я завжди tupleмав на увазі набір значень фіксованого розміру, кожне з потенційно різних типів, які не можна додавати або вилучати з. Я ніколи не розумів, чому потрібна динамічна мова зі списками, які приймають неоднорідні введення.
KChaloux

3

Існує загальний набір кроків, які можна використовувати для поліпшення читабельності функціональних алгоритмів:

  • Поставте імена на проміжні результати, замість того, щоб намагатися втиснути все в один рядок.
  • Використовуйте іменовані функції замість лямбда, особливо в мовах із багатослівним синтаксисом лямбда. Набагато простіше читати щось на кшталт, evaluateTermніж довгий лямбдаський вираз. Тільки тому, що ви можете використовувати лямбда, не обов'язково означає, що вам слід .
  • Якщо одна з названих вами тепер функцій виглядає як щось, що з’являється досить часто, швидше за все, це вже в стандартній бібліотеці. Подивись навколо. Мій пітон трохи іржавий, але схоже, що ви в основному винаходили enumerateабо zipWith.
  • Часто перегляд названих функцій та проміжних результатів полегшує міркування про те, що відбувається, і спрощення його, і в цей момент може бути доцільним повернути лямбда назад або об'єднати деякі рядки назад.
  • Якщо імператив для циклу виглядає більш читабельним, шанси на розуміння спрацюють добре.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.