함수형 프로그래밍의 'fold'함수에 해당하는 'pythonic'은 무엇입니까?


115

Haskell에서 다음과 같은 것을 달성하는 가장 관용적 인 방법은 무엇입니까?

foldl (+) 0 [1,2,3,4,5]
--> 15

또는 Ruby에서 이에 상응하는 것 :

[1,2,3,4,5].inject(0) {|m,x| m + x}
#> 15

분명히 파이썬은 reduce위와 똑같이 폴드 구현 인 함수를 제공 하지만, 프로그래밍의 'pythonic'방식은 가능한 경우 lambda목록 이해를 선호하는 용어와 고차 함수 를 피하는 것이라고 들었습니다 . 따라서 reduce함수 가 아닌 Python에서 목록 또는 목록과 유사한 구조를 접는 선호하는 방법 reduce이 있습니까? 아니면 이것을 달성하는 관용적 방법입니까?


2
sum충분하지 않습니까?
JBernardo

3
이것이 귀하의 질문에 대한 좋은 예인지 확실하지 않습니다. 로 쉽게 얻을 수 있으며 sum몇 가지 다른 유형의 예제를 제공 할 수 있습니다.
jamylak 2012

14
Hey JBernardo-숫자 목록을 합산하는 것은 다소 퇴보적인 예를 의미했습니다. 저는 정수를 구체적으로 합하는 것이 아니라 시작 값을 사용하여 목록의 요소를 누적하는 일반적인 아이디어에 더 관심이 있습니다.
mistertim

1
@mistertim : sum()실제로 이것으로 제한된 기능을 제공합니다. 예를 들어 sum([[a], [b, c, d], [e, f]], [])반환 [a, b, c, d, e, f]합니다.
Joel Cornett 2012

목록을 사용하는 경우는이 기술을 사용하여주의해야 할 사항을 잘 보여 주지만 +목록에 대한 작업은 시간과 메모리 모두에서 선형 시간 연산이므로 전체 호출을 2 차로 만듭니다. 사용 list(itertools.chain.from_iterable([a], [b,c,d],[e,f],[]])은 전체적으로 선형 적이며 한 번만 반복해야하는 경우 호출을 삭제하여 list메모리 측면에서 일정하게 만들 수 있습니다.
lvc 2013 년

답변:


114

배열을 합하는 Pythonic 방법은 sum. 다른 목적을 위해 때때로 reduce( functools모듈에서)와 모듈의 조합을 사용할 수 있습니다 operator. 예 :

def product(xs):
    return reduce(operator.mul, xs, 1)

그주의 reduce사실이다 foldl하스켈 측면에서. 폴드를 수행하는 특별한 구문도없고 내장도 없으며 foldr실제로 reduce비 연관 연산자와 함께 사용 하는 것은 잘못된 스타일로 간주됩니다.

고차 함수를 사용하는 것은 매우 비단뱀 적입니다. 함수와 클래스를 포함하여 모든 것이 객체라는 파이썬의 원칙을 잘 활용합니다. 람다가 일부 Pythonistas에 의해 눈살을 찌푸리는 것은 맞지만, 대부분 복잡해지면 읽기가 쉽지 않기 때문입니다.


4
@JBernardo : 내장 모듈에없는 것은 파이썬 적이 지 않다는 말입니까?
Fred Foo

4
아니, 그렇게 말하는 것은 어리석은 일입니다. 하지만 왜 GvR이 내장 기능 을 제거 할 때 축소 기능싫어할 것이라고 생각 합니까?
JBernardo

6
@JBernardo : 사람들이 너무 똑똑한 트릭을 사용하려고하기 때문입니다. 그 블로그 게시물에서 인용하자면, "의 적용 가능성은 reduce()연관 연산자로 거의 제한되며 다른 모든 경우에는 축적 루프를 명시 적으로 작성하는 것이 좋습니다." 따라서 사용은 제한적이지만 GvR조차도 표준 라이브러리에 보관할 수있을만큼 유용하다는 것을 인정해야했습니다.
Fred Foo

13
@JBernardo, 그렇다면 Haskell과 Scheme에서 폴드의 모든 사용이 똑같이 나쁘다는 의미입니까? 그것은 단지 다른 스타일의 프로그래밍 일 뿐이며, 그것을 무시하고 귀에 손가락을 대고 그것이 명확하지 않다고 말하는 것은 그렇게되지 않습니다. 스타일이 다른 대부분의 것들처럼 익숙해지기 위해서는 연습이 필요합니다 . 아이디어는 프로그램에 대해 더 쉽게 추론 할 수 있도록 일반적인 범주로 분류하는 것입니다. "Oh I want to do this, hmm, looks like a fold"(or a map, or an unfold, or an unfold then fold over that)
Wes

3
Python의 Lambda는 둘 이상의 표현식을 포함 할 수 없습니다. 열심히 노력해도 복잡하게 만들 수 없습니다. 따라서 그들을 좋아하지 않는 Pythonistas는 아마도 익숙하지 않아서 함수형 프로그래밍 스타일을 좋아하지 않을 것입니다.
골렘

16

Haskell

foldl (+) 0 [1,2,3,4,5]

파이썬

reduce(lambda a,b: a+b, [1,2,3,4,5], 0)

분명히 그것은 요점을 설명하는 사소한 예입니다. 파이썬에서는 그냥 할 sum([1,2,3,4,5])것이고 심지어 Haskell 순수 주의자들도 일반적으로 선호 할 것 sum [1,2,3,4,5]입니다.

명백한 편의 기능이없는 사소하지 않은 시나리오의 경우 관용적 파이썬 접근 방식은 for 루프를 명시 적으로 작성하고 reduce또는 을 사용 하는 대신 가변 변수 할당을 사용 하는 것 fold입니다.

그것은 전혀 기능적인 스타일은 아니지만 "파이썬"방식입니다. Python은 기능적 순수 주의자를 위해 설계되지 않았습니다. 파이썬이 어떻게 작동하지 않는 관용적 파이썬인지 확인하려면 흐름 제어에 대한 예외를 선호하는 방법을 참조하십시오.


12
주름은 기능적 "순수 자"보다 더 유용합니다. 범용 추상화입니다. 재귀 문제는 컴퓨팅에서 만연합니다. 접기는 상용구를 제거하는 방법과 기본적으로 재귀를 지원하지 않는 언어에서 재귀 솔루션을 안전하게 만드는 방법을 제공합니다. 그래서 매우 실용적인 것입니다. 이 분야에 대한 GvR의 편견은 불행합니다.
itsbruce

12

Python 3에서는 reduce이 제거되었습니다. 릴리스 정보 . 그럼에도 불구하고 functools 모듈을 사용할 수 있습니다.

import operator, functools
def product(xs):
    return functools.reduce(operator.mul, xs, 1)

반면에 문서는 for대신 -loop에 대한 선호를 표현 reduce하므로 다음과 같습니다.

def product(xs):
    result = 1
    for i in xs:
        result *= i
    return result

7
reducePython 3 표준 라이브러리에서 제거되지 않았습니다. 보여 주듯이 모듈로 reduce이동했습니다 functools.
clay

@clay, 방금 Guido의 릴리스 노트에서 문구를 가져 왔지만 당신이 맞을 수 있습니다 :)
Kyr

5

휠을 재발 명 할 수도 있습니다.

def fold(f, l, a):
    """
    f: the function to apply
    l: the list to fold
    a: the accumulator, who is also the 'zero' on the first call
    """ 
    return a if(len(l) == 0) else fold(f, l[1:], f(a, l[0]))

print "Sum:", fold(lambda x, y : x+y, [1,2,3,4,5], 0)

print "Any:", fold(lambda x, y : x or y, [False, True, False], False)

print "All:", fold(lambda x, y : x and y, [False, True, False], True)

# Prove that result can be of a different type of the list's elements
print "Count(x==True):", 
print fold(lambda x, y : x+1 if(y) else x, [False, True, True], 0)

f재귀 사례에서 인수를 교체합니다 .
KayEss

7
파이썬에는 꼬리 재귀가 없기 때문에 이것은 더 긴 목록에서 깨지고 낭비입니다. 또한,이는 "배"기능을하지만, 즉 foldl, 즉, 왼쪽 배입니다 단지 진정하지 정확히 무엇 reduce이다의 함수 서명을 줄이기 이미 제공 (참고 reduce(function, sequence[, initial]) -> value, 그것을, 너무에 대한 초기 값을주는 기능을 포함 - 누산기).
cemper93 2014

5

질문에 대한 답은 아니지만 foldl 및 foldr에 대한 한 줄 :

a = [8,3,4]

## Foldl
reduce(lambda x,y: x**y, a)
#68719476736

## Foldr
reduce(lambda x,y: y**x, a[::-1])
#14134776518227074636666380005943348126619871175004951664972849610340958208L

2
폴더를 작성하는 더 좋은 방법이라고 생각합니다 reduce(lambda y, x: x**y, reversed(a)).. 이제 더 자연스러운 사용이 가능하고 반복기와 함께 작동하며 메모리를 덜 소비합니다.
Mateen Ulhaq 2018 년

5

시작 Python 3.8할당 표현식 소개 (PEP 572) (:= 연산자) 결과의 이름을 지정할 수 있습니다. 목록 이해를 사용하여 다른 언어에서 fold / foldleft / reduce 작업을 호출하는 것을 복제 할 수 있습니다.

목록, 축소 함수 및 누산기가 제공됩니다.

items = [1, 2, 3, 4, 5]
f = lambda acc, x: acc * x
accumulator = 1

우리는 접을 수 itemsf결과를 얻기 위해 accumulation:

[accumulator := f(accumulator, x) for x in items]
# accumulator = 120

또는 응축 된 형태 :

acc = 1; [acc := acc * x for x in [1, 2, 3, 4, 5]]
# acc = 120

목록 이해의 결과는 각 단계의 누적 상태를 나타내므로 실제로 "scanleft"작업이기도합니다.

acc = 1
scanned = [acc := acc * x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 2, 6, 24, 120]
# acc = 120

3

이 (줄이기) 문제에 대한 실제 대답은 : 그냥 루프를 사용하십시오!

initial_value = 0
for x in the_list:
    initial_value += x #or any function.

이것은 감소보다 빠르며 PyPy와 같은 것들은 루프를 최적화 할 수 있습니다.

BTW, 합계 케이스는 sum함수 로 해결해야 합니다.


4
이것은 이와 같은 예에서 비단뱀으로 간주되지 않습니다.
jamylak 2012

7
파이썬 루프는 매우 느립니다. 사용 (또는 남용) reduce은 Python 프로그램을 최적화하는 일반적인 방법입니다.
Fred Foo

1
@larsmans 제발, reduce가 단순한 루프보다 빠르다고 말하지 마십시오 ... 항상 각 반복마다 함수 호출 오버 헤드가있을 것입니다. 또한 Pypy는 루프를 C 속도로 최적화 할 수 있습니다.
JBernardo 2012

1
@JBernardo : 네, 그게 제가 주장하는 바입니다. 방금 product귀하의 스타일 에 대해 내 버전을 프로파일 링 했으며 속도가 더 빠릅니다 (하지만 약간).
Fred Foo

1
@JBernardo (와 같은 operator.add) 내장 함수 를 줄이기위한 인수로 가정 : 추가 호출은 C 호출 (파이썬 호출보다 훨씬 저렴함)이며 몇 개의 바이트 코드 명령을 디스패치하고 해석하는 시간을 절약 할 수 있습니다. 함수 호출.

1

나는이 질문의 일부 응답자들이 fold추상 도구로서의 기능 의 광범위한 의미를 놓쳤다 고 생각합니다 . 예, sum정수 목록에 대해 동일한 작업을 수행 할 수 있지만 이것은 사소한 경우입니다. fold더 일반적입니다. 다양한 형태의 데이터 구조 시퀀스가 ​​있고 집계를 깔끔하게 표현하려는 경우에 유용합니다. 따라서 for집계 변수 로 루프 를 구축하고 매번 수동으로 다시 계산할 필요없이 fold함수 (또는 reduce해당 하는 것으로 보이는 Python 버전 )를 사용하면 프로그래머가 단순히 다음을 제공함으로써 집계의 의도를 훨씬 더 명확하게 표현할 수 있습니다. 두가지:

  • 집계의 기본 시작 또는 "시드"값입니다.
  • 집계의 현재 값 ( "seed"로 시작)과 목록의 다음 요소를 가져 와서 다음 집계 값을 반환하는 함수입니다.

안녕 rq_! fold파이썬에서 깔끔하게하기 어려운 사소하지 않은 예제를 제공 한 다음 파이썬에서 " fold":-)
Scott Skiles

0

나는 파티에 꽤 늦을 수도 있지만 foldr간단한 람다 미적분과 카레 함수를 사용하여 커스텀 을 만들 수 있습니다 . 다음은 파이썬으로 foldr를 구현 한 것입니다.

def foldr(func):
    def accumulator(acc):
        def listFunc(l):
            if l:
                x = l[0]
                xs = l[1:]
                return func(x)(foldr(func)(acc)(xs))
            else:
                return acc
        return listFunc
    return accumulator  


def curried_add(x):
    def inner(y):
        return x + y
    return inner

def curried_mult(x):
    def inner(y):
        return x * y
    return inner

print foldr(curried_add)(0)(range(1, 6))
print foldr(curried_mult)(1)(range(1, 6))

구현이 재귀 적이지만 (느릴 수 있음) 값 15120각각 인쇄합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.