이 문제에 대한 순수한 기능적 해결책이 명령만큼이나 깨끗할 수 있습니까?


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

명령형으로 읽을 수 있고 효율성에 가까운 순수한 기능 솔루션이 있습니까?

분명히, 표현 변화가 도움이 될 것이지만, 이것은 운동에 의해 주어졌습니다.

Python뿐만 아니라 Haskell 또는 Lisp도 가능합니다.


7
내 경험상, 가변 변수를 사용하지 않는다는 의미에서 순수하게 기능적인 코드는 ( for예를 들어 루프를 사용하지 않음을 의미 ) 파이썬에서 목표로하는 나쁜 목표입니다. 변수를 신중하게 리 바인드 하고 객체 를 변경하지 않으면 거의 모든 이점을 얻을 수 있으며 코드를 더 읽기 쉽게 만듭니다. 숫자 객체는 변경할 수 없으며 두 개의 로컬 이름 만 리 바인드하기 때문에 "제국적인"솔루션은 "엄격히 순수한"파이썬 코드보다 기능적 프로그래밍 미덕을 더 잘 실현합니다.

2
BTW 곱셈 방법은 Horner의 방법이며, 지수화에는 매우 동일한 곱셈이 필요하고 그 이상이 필요하기 때문에 각 단계에서 지수보다 효율적입니다.

1
lambda더 가벼운 익명 구문 함수 를 사용 하는 언어와 비교 하여 파이썬을 사용하면 파이썬이 다소 추악 합니다. 그 중 일부는 아마도 "고약한"외모에 기여합니다.
KChaloux

@KChaloux 그것이 바로 내가 말하려는 것입니다. 함수형 프로그래밍 지원은 많은 측면에서 파이썬에서 다소 나중에 생각할 수 있으며 일종의 쇼입니다. 그럼에도 불구하고 첫 번째 버전조차도 끔찍하게 읽을 수 없어서 무슨 일이 일어나고 있는지 알 수 없다고 생각합니다.
Evicatos

문제의 범위는 매우 분명한 수학 방정식을 가지고 있습니다. 왜 수학 방정식을 그대로 사용하지 않습니까? 그것은 어떤 언어로든 함수로 쉽게 변할 수 있습니다 ... 질문이 단일 방정식을 평가하고 그 방정식을 제공하는 함수를 요구할 때 무엇을 매핑하거나 축소하거나 반복 할 것인지 확실하지 않습니다-그것은 요구하지 않습니다 모두의 반복 ...
지미 호파

답변:


13

Horner의 방법은 @delnan이 지적한 것처럼 계산 방식이 더 효율적이지만 지수 솔루션을 위해 파이썬에서 이것을 읽을 수 있습니다.

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. 또한 특정 형태의 for 루프라고 생각할 수 있지만 해당 형태의 루프는 앞에서 언급 한 funcitonal combinator와 같습니다.

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)

때로는 haskell의 순진한 간단한 접근 방식이 FP에 익숙하지 않은 사람들에 대한보다 간결한 접근 방식보다 깨끗합니다.

여전히 완전히 순수한보다 분명한 명령 방식은 다음과 같습니다.

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)

두 번째 접근법에 대한 보너스는 ST 모나드에 있으며 매우 잘 수행됩니다.

확실하지만 Haskeller의 실제 구현은 위의 다른 답변에서 언급 한 zip입니다. zipWith매우 일반적인 접근 방식이며 파이썬이 함수와 매핑 가능한 인덱서를 결합하는 압축 방법을 모방 할 수 있다고 생각합니다.


4

당신이 경우 단지 A (고정) 튜플을 가지고, 왜 (하스켈)이 작업을 수행 할 :

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

파이썬에서 시간의 절반 (이 경우 포함)에서 "튜플"은 "불변 목록"을 의미하므로 임의 길이입니다.

명백히 임의의 길이
user1358

1
파이썬 때문에가 아니라 다항식이 임의의 길이를 암시하고 고정 크기가 큰 연습이되지 않기 때문에
user1358

1
@delnan 흥미 롭습니다. 나는 항상 tuple잠재적으로 다른 유형의 고정 크기 값 세트를 추가하거나 제거 할 수없는 의미로 사용했습니다. 이질적인 입력을 허용하는 목록이있는 동적 언어에 왜 필요한지 전혀 이해하지 못했습니다.
KChaloux

3

기능적 알고리즘의 가독성을 향상시키기 위해 사용할 수있는 일반적인 단계가 있습니다.

  • 모든 것을 한 줄에 넣는 대신 중간 결과에 이름을 입력하십시오.
  • 특히 람다 구문이 많은 언어에서는 람다 대신 명명 된 함수를 사용하십시오. evaluateTerm긴 람다 식과 같은 것을 읽는 것이 훨씬 쉽습니다 . 그냥 당신이 있기 때문에 할 수 반드시 의미하지 않는다 람다를 사용 해야한다 .
  • 현재 이름이 지정된 함수 중 하나가 자주 나타나는 함수처럼 보이면 이미 표준 라이브러리에있을 가능성이 있습니다. 둘러 봐 내 파이썬은 조금 녹슬지 만 기본적으로 enumerate또는을 재창조 한 것처럼 보입니다 zipWith.
  • 종종 이름이 지정된 함수와 중간 결과를 보면 상황을 추론하고 단순화하기가 더 쉬워집니다.이 시점에서 람다를 다시 넣거나 일부 줄을 다시 결합하는 것이 좋습니다.
  • 명령형 for 루프가 더 읽기 쉬워 보일 경우 이해가 잘 될 가능성이 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.