İşlevsel programlamadaki 'katlama' işlevine eşdeğer 'pitonik' nedir?


118

Haskell'de aşağıdakine benzer bir şeyi başarmanın en deyimsel yolu nedir:

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

Veya Ruby'deki karşılığı:

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

Açıkçası, Python, reducekatlamanın bir uygulaması olan işlevi tam olarak yukarıdaki gibi sağlar, ancak bana 'pitonik' programlama yolunun, lambdamümkün olan yerlerde liste kavrayışlarını tercih ederek terimlerden ve üst düzey işlevlerden kaçınmak olduğu söylendi . Bu nedenle, Python'da bir listeyi bölmenin tercih edilen bir yolu veya liste benzeri yapı var mıdır reduce, bu işlev değil mi , yoksa reducebunu başarmanın deyimsel yolu mu?


2
sumyeterince iyi değil mi?
JBernardo

3
Bunun sorunuz için iyi bir örnek olup olmadığından emin değilim. Kolaylıkla başarılabilir sum, bazı farklı türlerde örnekler vermek isteyebilirsiniz.
jamylak

14
Hey JBernardo - Bir sayılar listesinin toplanması oldukça yozlaşmış bir örnek olarak kastedildi, tam sayıları özel olarak toplamak yerine, bazı ikili işlemler ve bir başlangıç ​​değeri kullanarak bir listenin öğelerini toplama genel fikri ile daha çok ilgileniyorum.
mistertim

1
@mistertim: sum()aslında bununla sınırlı işlevsellik sağlar. örneğin sum([[a], [b, c, d], [e, f]], [])döner [a, b, c, d, e, f].
Joel Cornett

Listelerle yapmanın durumu, bu teknikle izlenecek şeylerin iyi bir gösterimi olsa da - +listelerde, hem zamanda hem de bellekte doğrusal bir zaman işlemidir ve tüm aramayı ikinci dereceden yapar. Kullanımı list(itertools.chain.from_iterable([a], [b,c,d],[e,f],[]])genel olarak doğrusaldır - ve üzerinde yalnızca bir kez yinelemeniz gerekiyorsa, çağrıyı listhafıza açısından sabit hale getirmek için bırakabilirsiniz .
lvc

Yanıtlar:


116

Bir diziyi toplamanın Pythonic yolu kullanmaktır sum. Diğer amaçlar için, bazen reduce( functoolsmodülden) ve modülün bazı kombinasyonlarını kullanabilirsiniz operator, örneğin:

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

Haskell açısından reducebunun aslında a olduğunu unutmayın foldl. Katlamaları gerçekleştirmek için özel bir sözdizimi yoktur, yerleşik yoktur foldrve aslında reduceilişkisel olmayan işleçlerle kullanmak kötü stil olarak kabul edilir.

Üst düzey işlevleri kullanmak oldukça pitoniktir; Python'un işlevler ve sınıflar dahil her şeyin bir nesne olduğu ilkesini iyi kullanır. Lambdaların bazı Pythonistalar tarafından hoş karşılanmadığı konusunda haklısınız, ancak çoğunlukla karmaşık olduklarında çok okunaklı olmadıkları için.


4
@JBernardo: Yerleşik modülde olmayan hiçbir şeyin pitonik olmadığını mı söylüyorsunuz?
Fred Foo

4
Hayır, bunu söylemek aptalca olur. Ama bana tek bir neden verin , GvR'nin yerleşiklerden kaldırılması noktasında indirgeme işlevinden neden bu kadar nefret ettiğini düşünüyorsunuz ?
JBernardo

6
@JBernardo: çünkü insanlar onunla çok akıllı oyunlar oynamaya çalışıyor. Bu blog gönderisinden alıntı reduce()yapacak olursak, "uygulanabilirliği ilişkisel operatörlerle oldukça sınırlıdır ve diğer tüm durumlarda birikim döngüsünü açıkça yazmak daha iyidir." Dolayısıyla, kullanımı sınırlıdır, ancak görünüşe göre GvR bile onu standart kitaplıkta tutmak için yeterince yararlı olduğunu kabul etmek zorunda kaldı.
Fred Foo

13
@JBernardo, bu Haskell ve Scheme'deki her fold kullanımının eşit derecede kötü olduğu anlamına mı geliyor? Bu sadece farklı bir programlama tarzı, onu görmezden gelmek ve parmaklarınızı kulağınıza koymak ve bunun net olmadığını söylemek bunu yapmaz. Farklı tarzdaki çoğu şey gibi, alışmak da pratik gerektirir . Buradaki fikir, şeyleri genel kategorilere ayırmak, böylece programlar hakkında akıl yürütmeyi kolaylaştırmaktır. "Oh, bunu yapmak istiyorum, hmm, bir kıvrım gibi görünüyor" (veya bir harita veya bir açılım, veya bir açılma sonra bunun üzerine bir katlama)
Wes

3
Python'daki Lambda birden fazla ifade içeremez. Çok uğraşsanız bile karmaşık hale getiremezsiniz. Dolayısıyla, onlardan hoşlanmayan Pythonistalar muhtemelen alışkın değillerdir ve bu nedenle işlevsel programlama stilini sevmezler.
golem

17

Haskell

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

piton

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

Açıkçası, bu bir noktayı açıklamak için önemsiz bir örnek. Python'da sadece yaparsınız sum([1,2,3,4,5])ve Haskell uzmanları bile genellikle tercih ederdi sum [1,2,3,4,5].

Belirgin bir kolaylık işlevi olmadığında önemsiz olmayan senaryolar için, deyimsel pitonik yaklaşım, for döngüsünü açıkça yazmak ve reduceveya a kullanmak yerine değişken değişken atamasını kullanmaktır fold.

Bu hiç de işlevsel tarz değil, ama bu "pitonik" yoldur. Python, işlevsel sadelik uzmanları için tasarlanmamıştır. Python'un işlevsel olmayan deyimsel python'u görmek için akış kontrolü için istisnaları nasıl tercih ettiğini görün.


12
kıvrımlar, işlevsel "sadelikten" daha fazlası için yararlıdır. Genel amaçlı soyutlamalardır. Özyinelemeli sorunlar, hesaplamada yaygındır. Kıvrımlar, standart metni kaldırmanın ve özyinelemeyi yerel olarak desteklemeyen dillerde özyinelemeli çözümleri güvenli hale getirmenin bir yolunu sunar. Yani çok pratik bir şey. GvR'nin bu alandaki önyargıları talihsizdir.
itsbruce

12

Python 3'te reduceşu kaldırılmıştır: Sürüm notları . Yine de functools modülünü kullanabilirsiniz

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

Öte yandan, dokümantasyon tercihi for-döngü yerine -döngü yerine ifade eder reduce, dolayısıyla:

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

9
reducePython 3 standart kitaplığından kaldırılmadı. gösterdiğiniz gibi modüle reducetaşındı functools.
toprak

1
@clay, Guido'nun sürüm notlarından az önce cümleyi aldım, ama haklı olabilirsin :)
Kyr

7

Başlangıç Python 3.8ve bir ifadenin sonucunu isimlendirme imkanı veren atama ifadelerinin (PEP 572) ( :=operatör) tanıtılması, diğer dillerin katlama / bırakma / azaltma işlemleri dediği şeyi çoğaltmak için bir liste kavrama kullanabiliriz:

Bir liste, bir indirgeme işlevi ve bir toplayıcı verildiğinde:

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

Biz katlayabilir itemsile fhusule elde etmek amacıyla accumulation:

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

veya yoğunlaştırılmış bir şekilde:

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

Liste kavrama sonucunda her adımda birikimin durumunu temsil ettiğinden, bunun aslında bir "sol tarama" işlemi olduğunu unutmayın:

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

5

Tekerleği de yeniden keşfedebilirsiniz:

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Özyinelemeli durumunuzda argümanları yaklaşık olarak değiştirirsiniz .
KayEss

7
Python'da kuyruk özyinelemesi olmadığından, bu daha uzun listelerde bozulacak ve israfa neden olacaktır. Dahası, bu gerçekten "katlama" işlevi değil, yalnızca bir sol katlama, yani foldl, yani tam olarakreduce sunulan şeydir (azaltmanın işlev imzası olduğunu unutmayın reduce(function, sequence[, initial]) -> value- bu, aynı zamanda, akümülatör).
cemper93

5

Aslında soruya cevap değil, foldl ve foldr için tek gömlek:

a = [8,3,4]

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

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

3
Bu e-postanın foldr yazmak için daha iyi bir yolu olduğunu düşünüyorum: reduce(lambda y, x: x**y, reversed(a)). Artık daha doğal bir kullanıma sahip, yineleyicilerle çalışıyor ve daha az bellek tüketiyor.
Mateen Ulhaq

2

Bu (azaltma) sorunun asıl cevabı şudur: Sadece bir döngü kullanın!

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

Bu, bir azaltmadan daha hızlı olacaktır ve PyPy gibi şeyler, döngüleri bu şekilde optimize edebilir.

BTW, toplam durum sumfonksiyonu ile çözülmelidir


5
Bunun gibi bir örnek için bu pitonik olarak kabul edilmez.
jamylak

7
Python döngüleri herkesin bildiği gibi yavaştır. Kullanmak (veya kötüye kullanmak) reduce, bir Python programını optimize etmenin yaygın bir yoludur.
Fred Foo

2
@larsmans Lütfen, azaltmanın basit bir döngüden daha hızlı olduğunu söylemeye gelmeyin ... Her yineleme için her zaman bir işlev çağrısı ek yükü olacaktır. Ayrıca, yine, Pypy döngüleri C hızına optimize edebilir
JBernardo

1
@JBernardo: evet, iddia ettiğim şey bu. productSenin tarzına göre kendi versiyonumun profilini çıkardım ve daha hızlı (marjinal olsa da).
Fred Foo

1
@JBernardo Azaltılacak operator.addargüman olarak yerleşik bir işlevi (gibi ) varsayarsak : Bu ekstra çağrı bir C çağrısıdır (Python çağrısından çok daha ucuzdur) ve birkaç bayt kodu talimatının gönderilmesini ve yorumlanmasını kaydeder, bu da düzinelerce işlev çağrıları.

1

Bu soruyu yanıtlayanların bir kısmının, foldfonksiyonun soyut bir araç olarak daha geniş anlamını gözden kaçırdığına inanıyorum . Evet, sumaynı şeyi bir tamsayı listesi için de yapabilir, ancak bu önemsiz bir durumdur. folddaha geneldir. Çeşitli şekillerde bir dizi veri yapınız olduğunda ve bir toplamayı temiz bir şekilde ifade etmek istediğinizde kullanışlıdır. Bu nedenle for, bir toplu değişkenle bir döngü oluşturmak ve her seferinde manuel olarak yeniden hesaplamak zorunda kalmak foldyerine , bir işlev (veya reducekarşılık geliyor gibi görünen Python sürümü ), programcının basitçe sağlayarak toplamanın amacını çok daha açık bir şekilde ifade etmesini sağlar. iki şey:

  • Toplama için varsayılan bir başlangıç ​​veya "tohum" değeri.
  • Toplamanın geçerli değerini ("tohum" ile başlayan) ve listedeki sonraki öğeyi alan ve sonraki toplama değerini döndüren bir işlev.

Merhaba rq_! foldPython'da temiz bir şekilde yapılması zor olan önemsiz olmayan bir örnek verirseniz ve ardından " fold" bunu Python'da :-)
Scott Skiles

0

Partiye oldukça geç kalmış olabilirim, ancak foldrbasit lambda hesabı ve curried işlevi kullanarak özel oluşturabiliriz . İşte benim python'daki foldr uygulamam.

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))

Uygulama özyinelemeli olsa da (yavaş olabilir), değerleri 15ve 120sırasıyla

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.