Kode berguna yang menggunakan reduce ()? [Tutup]


123

Apakah ada orang di sini yang memiliki kode berguna yang menggunakan fungsi reduce () di python? Apakah ada kode selain + dan * biasa yang kita lihat di contoh?

Lihat Fate of reduce () dengan Python 3000 oleh GvR


1
from functools import reducememungkinkan kode yang sama untuk bekerja pada Python 2 dan 3.
jfs

Jawaban:


66

Kegunaan lain yang saya temukan untuk itu selain + dan * dulu dengan dan atau, tetapi sekarang kami memiliki anydan allmengganti kasing tersebut.

foldldan foldrsering muncul di Skema ...

Inilah beberapa penggunaan lucu:

Ratakan daftar

Sasaran: berubah [[1, 2, 3], [4, 5], [6, 7, 8]]menjadi [1, 2, 3, 4, 5, 6, 7, 8].

reduce(list.__add__, [[1, 2, 3], [4, 5], [6, 7, 8]], [])

Daftar digit angka

Sasaran: berubah [1, 2, 3, 4, 5, 6, 7, 8]menjadi 12345678.

Jelek, cara lambat:

int("".join(map(str, [1,2,3,4,5,6,7,8])))

reduceCara yang cantik :

reduce(lambda a,d: 10*a+d, [1,2,3,4,5,6,7,8], 0)

23
Untuk meratakan daftar, saya lebih suka daftar (itertools.chain (* nested_list))
Roberto Bonvallet

13
jumlah ([[1, 2, 3], [4, 5], [6, 7, 8]], [])
Gordon Wrigley

3
Ini juga berguna untuk operasi bitwise. Bagaimana jika Anda ingin mengambil bitwise atau sekumpulan angka, misalnya jika Anda perlu mengonversi flag dari daftar ke bitmask?
Antimony

6
Melakukan beberapa tolok ukur, cara 'jelek' lebih cepat untuk daftar besar. timeit.repeat('int("".join(map(str, digit_list)))', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)membutuhkan ~ 0,09 detik sementara timeit.repeat('reduce(lambda a,d: 10*a+d, digit_list)', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)membutuhkan 0,36 detik (sekitar 4 kali lebih lambat). Pada dasarnya perkalian dengan 10 menjadi mahal ketika daftarnya menjadi besar, sementara int ke str dan penggabungan tetap murah.
dr jimbob

3
Memang, ya untuk list kecil (ukuran 10) maka metode pengurangannya 1,3 kali lebih cepat. Namun, bahkan dalam kasus ini, menghindari pengurangan dan melakukan loop sederhana bahkan lebih cepat timeit.repeat('convert_digit_list_to_int(digit_list)', setup = 'digit_list = [d%10 for d in xrange(1,10)]\ndef convert_digit_list_to_int(digits):\n i = 0\n for d in digits:\n i = 10*i + d\n return i', number=100000)membutuhkan 0,06 detik, timeit.repeat('reduce(lambda a,d: 10*a+d, digit_list)', setup = 'digit_list = list(d%10 for d in xrange(1,10))', number=100000)membutuhkan 0,12 detik dan mengubah angka menjadi metode str membutuhkan 0,16 detik.
dr jimbob

51

reduce()dapat digunakan untuk mencari kelipatan persekutuan terkecil untuk 3 angka atau lebih :

#!/usr/bin/env python
from fractions import gcd
from functools import reduce

def lcm(*args):
    return reduce(lambda a,b: a * b // gcd(a, b), args)

Contoh:

>>> lcm(100, 23, 98)
112700
>>> lcm(*range(1, 20))
232792560

1
Apa yang ada lcmdi baris kedua?
beardc

1
@BirdJaguarIV: ikuti link di jawaban. lcm()mengembalikan kelipatan persekutuan terkecil dari dua angka.
jfs

39

reduce()dapat digunakan untuk menyelesaikan nama putus-putus ( eval()yang terlalu tidak aman untuk digunakan):

>>> import __main__
>>> reduce(getattr, "os.path.abspath".split('.'), __main__)
<function abspath at 0x009AB530>


12

Saya pikir mengurangi adalah perintah konyol. Karenanya:

reduce(lambda hold,next:hold+chr(((ord(next.upper())-65)+13)%26+65),'znlorabggbbhfrshy','')

1
Saya juga suka ironi di sini
Roman

11

Penggunaan reduceyang saya temukan dalam kode saya melibatkan situasi di mana saya memiliki beberapa struktur kelas untuk ekspresi logika dan saya perlu mengonversi daftar objek ekspresi ini ke sambungan ekspresi. Saya sudah memiliki fungsi make_anduntuk membuat hubungan dengan dua ekspresi, jadi saya menulis reduce(make_and,l). (Saya tahu daftarnya tidak kosong; jika tidak maka akan menjadi seperti ini reduce(make_and,l,make_true).)

Inilah alasan sebenarnya mengapa (beberapa) pemrogram fungsional menyukai reduce(atau melipat fungsi, seperti fungsi biasanya disebut). Ada sering sudah banyak fungsi biner seperti +, *, min, max, Rangkaian dan, dalam kasus saya, make_anddan make_or. Memiliki a reducemembuatnya mudah untuk mengangkat operasi ini ke daftar (atau pohon atau apa pun yang Anda dapatkan, untuk fungsi lipat secara umum).

Tentu saja, jika instansiasi tertentu (seperti sum) sering digunakan, maka Anda tidak ingin terus menulis reduce. Namun, alih-alih mendefinisikan sumdengan beberapa for-loop, Anda dapat dengan mudah mendefinisikannya dengan reduce.

Keterbacaan, seperti yang disebutkan oleh orang lain, memang menjadi masalah. Namun, Anda dapat berargumen bahwa satu-satunya alasan mengapa orang merasa reducekurang "jelas" adalah karena ini bukan fungsi yang diketahui dan / atau digunakan banyak orang.


untuk mencegah daftar kosong Anda dapat mengeksploitasi perilaku hubung singkat andoperator: L and reduce(make_and, L)jika mengembalikan daftar kosong sesuai dalam kasus ini
jfs

9

Komposisi fungsi : Jika Anda sudah memiliki daftar fungsi yang ingin Anda terapkan secara berurutan, seperti:

color = lambda x: x.replace('brown', 'blue')
speed = lambda x: x.replace('quick', 'slow')
work = lambda x: x.replace('lazy', 'industrious')
fs = [str.lower, color, speed, work, str.title]

Kemudian Anda dapat menerapkan semuanya secara berurutan dengan:

>>> call = lambda s, func: func(s)
>>> s = "The Quick Brown Fox Jumps Over the Lazy Dog"
>>> reduce(call, fs, s)
'The Slow Blue Fox Jumps Over The Industrious Dog'

Dalam kasus ini, rangkaian metode mungkin lebih mudah dibaca. Tetapi terkadang tidak mungkin, dan komposisi semacam ini mungkin lebih mudah dibaca dan dipelihara daripada f1(f2(f3(f4(x))))sejenis sintaks.


1
Keuntungannya adalah Anda dapat mengubah daftar fungsi yang akan diterapkan dalam kode.
hakanc


7

@Blair Conrad: Anda juga bisa menerapkan glob / reduce menggunakan jumlah, seperti:

files = sum([glob.glob(f) for f in args], [])

Ini kurang bertele-tele daripada salah satu dari dua contoh Anda, Pythonic sempurna, dan masih hanya satu baris kode.

Jadi untuk menjawab pertanyaan awal, saya pribadi mencoba menghindari penggunaan pengurangan karena tidak pernah benar-benar diperlukan dan menurut saya kurang jelas dibandingkan pendekatan lainnya. Namun, beberapa orang terbiasa mengurangi dan memilihnya untuk daftar pemahaman (terutama programmer Haskell). Tetapi jika Anda belum memikirkan suatu masalah dalam hal pengurangan, Anda mungkin tidak perlu khawatir untuk menggunakannya.


2
Keduanya sumdan reducemengarah pada perilaku kuadrat. Hal ini dapat dilakukan dalam waktu linier: files = chain.from_iterable(imap(iglob, args)). Meskipun mungkin tidak masalah dalam kasus ini karena waktu yang dibutuhkan untuk glob () mengakses disk.
jfs

6

reduce dapat digunakan untuk mendukung pencarian atribut yang dirantai:

reduce(getattr, ('request', 'user', 'email'), self)

Tentu saja, ini sama dengan

self.request.user.email

tetapi berguna ketika kode Anda perlu menerima daftar atribut yang berubah-ubah.

(Atribut berantai dengan panjang sembarang adalah umum ketika berhadapan dengan model Django.)


4

reduceberguna ketika Anda perlu menemukan penyatuan atau perpotongan urutan setobjek -seperti.

>>> reduce(operator.or_, ({1}, {1, 2}, {1, 3}))  # union
{1, 2, 3}
>>> reduce(operator.and_, ({1}, {1, 2}, {1, 3}))  # intersection
{1}

(Terlepas dari sets sebenarnya , contohnya adalah objek Q Django .)

Di sisi lain, jika Anda berurusan dengan bools, Anda harus menggunakan anydan all:

>>> any((True, False, True))
True


3

Saya sedang menulis fungsi compose untuk sebuah bahasa, jadi saya membuat fungsi compose menggunakan reduce bersama dengan operator apply saya.

Singkatnya, compose mengambil daftar fungsi untuk dibuat menjadi satu fungsi. Jika saya memiliki operasi kompleks yang diterapkan secara bertahap, saya ingin menggabungkan semuanya seperti ini:

complexop = compose(stage4, stage3, stage2, stage1)

Dengan cara ini, saya kemudian dapat menerapkannya ke ekspresi seperti ini:

complexop(expression)

Dan saya ingin itu setara dengan:

stage4(stage3(stage2(stage1(expression))))

Sekarang, untuk membangun objek internal saya, saya ingin mengatakan:

Lambda([Symbol('x')], Apply(stage4, Apply(stage3, Apply(stage2, Apply(stage1, Symbol('x'))))))

(Kelas Lambda membangun fungsi yang ditentukan pengguna, dan Apply membangun aplikasi fungsi.)

Sekarang, sayangnya, kurangi lipatan dengan cara yang salah, jadi saya akhirnya menggunakan, secara kasar:

reduce(lambda x,y: Apply(y, x), reversed(args + [Symbol('x')]))

Untuk mengetahui apa yang mengurangi produksi, coba ini di REPL:

reduce(lambda x, y: (x, y), range(1, 11))
reduce(lambda x, y: (y, x), reversed(range(1, 11)))

Saya telah menggunakan compose = lambda *func: lambda arg: reduce(lambda x, f: f(x), reversed(funcs), arg)untuk menghasilkan semua kemungkinan kombinasi dari fungsi untuk pengujian kinerja.
jfs

3

mengurangi dapat digunakan untuk mendapatkan daftar dengan elemen ke maksimum

reduce(lambda x,y: x if x[2] > y[2] else y,[[1,2,3,4],[5,2,5,7],[1,6,0,2]])

akan mengembalikan [5, 2, 5, 7] karena ini adalah daftar dengan maks elemen ke-3 +


max (lst, key = lambda x: x [2])
aoeu256

3

Mengurangi tidak terbatas pada operasi skalar; itu juga dapat digunakan untuk menyortir barang ke dalam ember. (Ini yang paling sering saya gunakan untuk mengurangi).

Bayangkan sebuah kasus di mana Anda memiliki daftar objek, dan Anda ingin menata ulang secara hierarki berdasarkan properti yang disimpan secara datar di objek. Dalam contoh berikut, saya membuat daftar objek metadata yang terkait dengan artikel di surat kabar berenkode XML dengan articlesfungsi tersebut. articlesmenghasilkan daftar elemen XML, dan kemudian memetakannya satu per satu, menghasilkan objek yang menyimpan beberapa info menarik tentangnya. Di bagian depan, saya ingin membiarkan pengguna menelusuri artikel berdasarkan bagian / subbagian / tajuk utama. Jadi saya gunakan reduceuntuk mengambil daftar artikel dan mengembalikan satu kamus yang mencerminkan hierarki bagian / subbagian / artikel.

from lxml import etree
from Reader import Reader

class IssueReader(Reader):
    def articles(self):
        arts = self.q('//div3')  # inherited ... runs an xpath query against the issue
        subsection = etree.XPath('./ancestor::div2/@type')
        section = etree.XPath('./ancestor::div1/@type')
        header_text = etree.XPath('./head//text()')
        return map(lambda art: {
            'text_id': self.id,
            'path': self.getpath(art)[0],
            'subsection': (subsection(art)[0] or '[none]'),
            'section': (section(art)[0] or '[none]'),
            'headline': (''.join(header_text(art)) or '[none]')
        }, arts)

    def by_section(self):
        arts = self.articles()

        def extract(acc, art):  # acc for accumulator
            section = acc.get(art['section'], False)
            if section:
                subsection = acc.get(art['subsection'], False)
                if subsection:
                    subsection.append(art)
                else:
                    section[art['subsection']] = [art]
            else:
                acc[art['section']] = {art['subsection']: [art]}
            return acc

        return reduce(extract, arts, {})

Saya memberikan kedua fungsi di sini karena menurut saya ini menunjukkan bagaimana map dan reduce dapat saling melengkapi dengan baik ketika berhadapan dengan objek. Hal yang sama dapat dilakukan dengan for loop, ... tetapi menghabiskan waktu serius dengan bahasa fungsional cenderung membuat saya berpikir dalam kerangka peta dan mengurangi.

Ngomong-ngomong, jika ada yang memiliki cara yang lebih baik untuk menyetel properti seperti yang saya lakukan extract, di mana orang tua properti yang ingin Anda setel mungkin belum ada, beri tahu saya.


3

Tidak yakin apakah ini yang Anda cari tetapi Anda dapat mencari kode sumber di Google .

Ikuti link untuk pencarian di 'function: reduce () lang: python' di pencarian Google Code

Sekilas penggunaan proyek berikut reduce()

  • MoinMoin
  • Zope
  • Numerik
  • ScientificPython

dll. dll tetapi kemudian ini tidak mengejutkan karena mereka adalah proyek besar.

Fungsi pengurangan dapat dilakukan dengan menggunakan rekursi fungsi yang menurut saya Guido pikir lebih eksplisit.

Memperbarui:

Sejak Pencarian Kode Google dihentikan pada 15-Jan-2012, selain kembali ke pencarian Google biasa, ada sesuatu yang disebut Koleksi Cuplikan Kode yang terlihat menjanjikan. Sejumlah sumber lain disebutkan dalam menjawab pertanyaan (tertutup) ini tentang Penggantian untuk Google Code Search? .

Pembaruan 2 (29-Mei-2017):

Sumber yang baik untuk contoh Python (dalam kode sumber terbuka) adalah mesin pencari Nullege .


1
"Fungsi pengurangan dapat dilakukan dengan menggunakan rekursi fungsi" ... Atau forloop.
Jason Orendorff

2
Juga, mencari reduce () menghasilkan proyek yang mendefinisikan fungsi pengurangan dalam kodenya. Anda harus mencari lang: python "reduce (" untuk menemukan penggunaan sebenarnya dari fungsi
bawaan

@Seun Osewa: Bahkan mencari lang:python "reduce("akan menemukan definisi reducetergantung pada gaya pengkodean kode sumber.
martineau

2
import os

files = [
    # full filenames
    "var/log/apache/errors.log",
    "home/kane/images/avatars/crusader.png",
    "home/jane/documents/diary.txt",
    "home/kane/images/selfie.jpg",
    "var/log/abc.txt",
    "home/kane/.vimrc",
    "home/kane/images/avatars/paladin.png",
]

# unfolding of plain filiname list to file-tree
fs_tree = ({}, # dict of folders
           []) # list of files
for full_name in files:
    path, fn = os.path.split(full_name)
    reduce(
        # this fucction walks deep into path
        # and creates placeholders for subfolders
        lambda d, k: d[0].setdefault(k,         # walk deep
                                     ({}, [])), # or create subfolder storage
        path.split(os.path.sep),
        fs_tree
    )[1].append(fn)

print fs_tree
#({'home': (
#    {'jane': (
#        {'documents': (
#           {},
#           ['diary.txt']
#        )},
#        []
#    ),
#    'kane': (
#       {'images': (
#          {'avatars': (
#             {},
#             ['crusader.png',
#             'paladin.png']
#          )},
#          ['selfie.jpg']
#       )},
#       ['.vimrc']
#    )},
#    []
#  ),
#  'var': (
#     {'log': (
#         {'apache': (
#            {},
#            ['errors.log']
#         )},
#         ['abc.txt']
#     )},
#     [])
#},
#[])

1
Bisakah Anda menambahkan sedikit penjelasan tentang apa yang terjadi di sini? Kalau tidak, kegunaannya sama sekali tidak jelas.
Zoran Pavlovic

2
def dump(fname,iterable):
  with open(fname,'w') as f:
    reduce(lambda x, y: f.write(unicode(y,'utf-8')), iterable)

2

Saya biasa reduce menggabungkan daftar vektor pencarian PostgreSQL dengan ||operator di sqlalchemy-searchable:

vectors = (self.column_vector(getattr(self.table.c, column_name))
           for column_name in self.indexed_columns)
concatenated = reduce(lambda x, y: x.op('||')(y), vectors)
compiled = concatenated.compile(self.conn)

1

Saya memiliki implementasi pipegrep Python lama yang menggunakan reduce dan modul glob untuk membuat daftar file untuk diproses:

files = []
files.extend(reduce(lambda x, y: x + y, map(glob.glob, args)))

Saya merasa itu berguna pada saat itu, tetapi sebenarnya tidak perlu, karena sesuatu yang serupa sama baiknya, dan mungkin lebih mudah dibaca

files = []
for f in args:
    files.extend(glob.glob(f))

Bagaimana dengan pemahaman daftar? Ini sepertinya aplikasi yang sempurna untuk itu: files = [glob.glob(f) for f in args]
steveha

Sebenarnya, @steveha, contoh Anda akan menghasilkan daftar glob yang diperluas, bukan daftar datar dari semua item yang cocok dengan glob, tetapi Anda dapat menggunakan pemahaman daftar + jumlah, seperti @ [Eli Courtwright] (# 16198 ) menunjukkan.
Blair Conrad

1
Oke, Anda benar, maaf soal itu. Saya masih kurang suka kombinasi perpanjang / kurangi / lambda / peta! Saya akan merekomendasikan mengimpor itertools, menggunakan flatten()resep dari docs.python.org/library/itertools.html , dan kemudian menulis: files = flatten(glob.glob(f) for f in args) (Dan kali ini, saya menguji kode sebelum mempostingnya, dan saya tahu ini berfungsi dengan benar.)
steveha

files = chain.from_iterable(imap(iglob, args))di mana chain, imapdari itertoolsmodul dan glob.iglobberguna jika pola dari argsdapat menghasilkan file dari beberapa direktori.
jfs

1

Katakanlah ada beberapa data statistik tahunan yang menyimpan daftar Penghitung. Kami ingin menemukan nilai MIN / MAX setiap bulan di tahun yang berbeda. Misalnya, untuk Januari akan menjadi 10. Dan untuk Februari akan menjadi 15. Kami perlu menyimpan hasilnya di Penghitung baru.

from collections import Counter

stat2011 = Counter({"January": 12, "February": 20, "March": 50, "April": 70, "May": 15,
           "June": 35, "July": 30, "August": 15, "September": 20, "October": 60,
           "November": 13, "December": 50})

stat2012 = Counter({"January": 36, "February": 15, "March": 50, "April": 10, "May": 90,
           "June": 25, "July": 35, "August": 15, "September": 20, "October": 30,
           "November": 10, "December": 25})

stat2013 = Counter({"January": 10, "February": 60, "March": 90, "April": 10, "May": 80,
           "June": 50, "July": 30, "August": 15, "September": 20, "October": 75,
           "November": 60, "December": 15})

stat_list = [stat2011, stat2012, stat2013]

print reduce(lambda x, y: x & y, stat_list)     # MIN
print reduce(lambda x, y: x | y, stat_list)     # MAX

1

Saya memiliki objek yang mewakili beberapa jenis interval yang tumpang tindih (ekson genom), dan mendefinisikan ulang persimpangannya menggunakan __and__:

class Exon:
    def __init__(self):
        ...
    def __and__(self,other):
        ...
        length = self.length + other.length  # (e.g.)
        return self.__class__(...length,...)

Kemudian ketika saya memiliki koleksi mereka (misalnya, dalam gen yang sama), saya gunakan

intersection = reduce(lambda x,y: x&y, exons)

1

Saya baru saja menemukan penggunaan berguna dari reduce: memisahkan string tanpa menghapus pembatas . Kode sepenuhnya dari blog Programatically Speaking. Berikut kodenya:

reduce(lambda acc, elem: acc[:-1] + [acc[-1] + elem] if elem == "\n" else acc + [elem], re.split("(\n)", "a\nb\nc\n"), [])

Inilah hasilnya:

['a\n', 'b\n', 'c\n', '']

Perhatikan bahwa itu menangani kasus tepi yang jawaban populer di SO tidak. Untuk penjelasan lebih mendalam, saya mengarahkan Anda ke postingan blog asli.


0

Menggunakan reduce () untuk mengetahui apakah daftar tanggal berurutan:

from datetime import date, timedelta


def checked(d1, d2):
    """
    We assume the date list is sorted.
    If d2 & d1 are different by 1, everything up to d2 is consecutive, so d2
    can advance to the next reduction.
    If d2 & d1 are not different by 1, returning d1 - 1 for the next reduction
    will guarantee the result produced by reduce() to be something other than
    the last date in the sorted date list.

    Definition 1: 1/1/14, 1/2/14, 1/2/14, 1/3/14 is consider consecutive
    Definition 2: 1/1/14, 1/2/14, 1/2/14, 1/3/14 is consider not consecutive

    """
    #if (d2 - d1).days == 1 or (d2 - d1).days == 0:  # for Definition 1
    if (d2 - d1).days == 1:                          # for Definition 2
        return d2
    else:
        return d1 + timedelta(days=-1)

# datelist = [date(2014, 1, 1), date(2014, 1, 3),
#             date(2013, 12, 31), date(2013, 12, 30)]

# datelist = [date(2014, 2, 19), date(2014, 2, 19), date(2014, 2, 20),
#             date(2014, 2, 21), date(2014, 2, 22)]

datelist = [date(2014, 2, 19), date(2014, 2, 21),
            date(2014, 2, 22), date(2014, 2, 20)]

datelist.sort()

if datelist[-1] == reduce(checked, datelist):
    print "dates are consecutive"
else:
    print "dates are not consecutive"
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.