Kursus
Fungsi reduce() di Python berakar dari dunia pemrograman fungsional. Pemrograman fungsional (FP) adalah paradigma pemrograman di mana program menghasilkan hasil dengan menerapkan fungsi pada data yang tidak dapat diubah.
Pola umum dalam gaya ini adalah "fold," yang meruntuhkan sebuah urutan menjadi satu hasil. Misalnya, mem-fold daftar angka [2, 4, 5, 3] dengan operasi penjumlahan menghasilkan 14 melalui langkah berturut-turut: [2, 4, 5, 3] → [6, 5, 3] → [11, 3] → 14.
reduce() menggeneralisasi gagasan ini. Ia menerapkan operasi biner pada sebuah iterable hingga hanya tersisa hasil akhirnya.
Dalam artikel ini, saya akan membahas elemen kunci dari reduce() di Python dan memberikan beberapa contoh praktis. Jika Anda perlu penyegaran dasar-dasar Python, saya sarankan meninjau sumber berikut:
- Python Slice: Metode Berguna untuk Koding Sehari-hari - tutorial
- Python for R Users - kursus
Sejarah
Python menyertakan fungsi pemrograman fungsional lain di Python, seperti map() dan filter().
Konstruk fungsional seperti map() dan filter() sudah ada di Python 1.0. Guido van Rossum kurang menyukainya, menyoroti bahwa reduce() sulit dibaca, dan for loop hampir selalu lebih mudah dipahami. Di Python 3.0, mengikuti PEP 3100, pengembang menghapus reduce() sebagai built-in dan memindahkannya ke modul functools. Pemindahan ke functools pada dasarnya menurunkannya menjadi alat untuk kasus khusus.
Mengapa Menggunakan reduce()?
Sebagian besar waktu, menurut saya pilihan yang lebih baik adalah menggunakan built-in atau loop. Namun, reduce() tetap menjadi pilihan yang baik dalam beberapa kasus penggunaan.
- Pipa fungsi. Merangkai serangkaian transformasi (mungkin dinamis) dengan cara yang rapi.
- Fold aljabar. Gunakan untuk operasi yang memiliki nilai identitas alami, seperti union himpunan dengan himpunan kosong atau operasi bitmask dengan nol.
- Fold kustom tanpa built-in. Definisikan penggabungan sendiri secara spesifik domain saat tidak ada built-in yang tersedia.
- Akumulator terstruktur. Lacak beberapa potongan state secara bersamaan di dalam satu fungsi akumulator kustom.
Cara Kerja reduce() di Python
Mari kita telusuri mekanisme reduce().
Tanda tangan fungsi dasar dari reduce() adalah
functools.reduce(function, iterable, [initializer])
Fungsi reduce() menerima dua argumen wajib dan satu argumen opsional.
function: fungsi biner yang menentukan cara menggabungkan dua elemen,iterable: urutan atau iterable yang akan direduksi, seperti list atau tuple,initializer(opsional): nilai awal untuk memberi seed pada fungsi.
Contoh langkah demi langkah:
- [1, 3, 2, 7] → [4, 2, 7].
- [4, 2, 7] → [6, 7].
- [6, 7] → [13].
Hasil akhirnya adalah 13.
Contoh sederhana reduce()
Contoh mainan berikut menunjukkan mekanisme cara menggunakan reduce().
from functools import reduce
numbers = [2, 4, 6]
product = reduce(lambda x, y: x * y, numbers) # ((2 x 4) x 6) = 8 x 6 = 48
min_value = reduce(lambda x, y: x if x < y else y, numbers) # 2
words = ['dog', 'cat', 'tree', 'pony']
str_concat = reduce(lambda x, y: x + y, words) # "dogcattreepony"
Initializer
Tanpa initializer, reduce() menggunakan elemen pertama dari iterable sebagai nilai awalnya. Jika iterable kosong, reduce() akan melempar TypeError. Untuk membuat kode tangguh, berikan initializer untuk mendefinisikan perilaku pada masukan kosong.
from functools import reduce
words = []
# Error: reduce() of empty iterable with no initial value
str_concat = reduce(lambda x, y: x + y, words)
# Correct: use empty string initializer
str_concat = reduce(lambda x, y: x + y, words, "")
Initializer juga memberi seed hasil dengan kontainer kosong. Misalnya, Anda dapat menggabungkan kata menjadi daftar karakter datar.
from functools import reduce
words = ['reduce', 'is', 'fun']
chars_list = reduce(lambda acc, word: acc + list(word), words, [])
print(chars_list) # ['r', 'e', 'd', 'u', 'c', 'e', 'i', 's', 'f', 'u', 'n']
Untuk mengeksplorasi Python dan manipulasi data lebih jauh, berikut beberapa opsi yang saya rekomendasikan.
- Introduction to Importing Data in Python - kursus
- Data Manipulation in Python - skill track
- Reshaping Data with pandas in Python - lembar contekan
- Dimensionality Reduction in Python - kursus
- Data Preprocessing: A Complete Guide with Python Examples - blog
Mendefinisikan reducer
Sejauh ini, kita menggunakan fungsi lambda untuk mendefinisikan operator biner.
Anda juga dapat menggunakan operator dari modul operator. Modul operator berisi versi fungsi dari operator umum dan pemanggilan metode. Misalnya, alih-alih x + y, gunakan operator.add(x, y). Ini memungkinkan Anda memasukkan operator siap pakai (dan efisien) ke reduce() tanpa perlu menulis lambda.
from functools import reduce
import operator as op
numbers = [2, 4, 6]
total = reduce(op.add, numbers, 0) # instead of reduce(lambda x,y: x + y, numbers)
Opsi ketiga adalah menulis fungsi kustom. Ini pilihan yang baik saat tidak ada operator yang sudah tersedia atau fungsinya terlalu rumit untuk lambda.
Sebagai contoh, misalkan Anda ingin menghapus duplikasi dari sebuah list, tetapi mempertahankan urutan kemunculan pertama. Anda dapat mendefinisikan reducer yang menambahkan item ke list hanya jika item tersebut belum muncul sebelumnya.
from functools import reduce
items = ['the', 'wild', 'wild', 'world', 'is', 'the', 'wide', 'world', 'is', 'the', 'world']
def dedup(acc, x):
if x not in acc: # O(n) membership test
acc.append(x)
return acc
unique = reduce(dedup, items, []) # ['the', 'wild', 'world', 'is', 'wide']
Untuk ide lebih lanjut tentang menghapus duplikasi, pertimbangkan tutorial ini.
Kinerja reduce() di Python
reduce() memiliki isu kinerja.
Overhead pemanggilan fungsi
Python reduce() memanggil fungsi kita sekali untuk setiap elemen. Pada list dengan satu juta item, ini berarti satu juta pemanggilan fungsi, masing-masing membuat frame, menangani argumen, dan memperbarui reference count. Ini menambah overhead yang signifikan.
Sebaliknya, built-in seperti sum melakukan satu pemanggilan ke fungsi C dan menjalankan sejuta penjumlahan di dalam loop C. Perbedaan ini dapat membuat built-in jauh lebih cepat.
Lokalitas cache dan efisiensi CPU
Reduce juga terkendala lokalitas cache dan efisiensi CPU.
CPU modern dapat mengeksekusi beberapa miliar instruksi per detik, tetapi akses RAM jauh lebih lambat. Untuk mengompensasi, CPU modern memiliki cache (L1, L2, L3) yang menyimpan data untuk akses cepat.
Cache ini mengeksploitasi dua pola.
- Lokalitas temporal: data yang baru digunakan kemungkinan akan digunakan lagi.
- Lokalitas spasial: data yang dekat dengan alamat yang baru digunakan kemungkinan akan segera digunakan.
Sebaliknya, setiap langkah reduce() melibatkan penelusuran pointer untuk menemukan elemen berikutnya dan pemanggilan fungsi Python, yang merusak lokalitas dan membuat CPU tersendat. Built-in dan fungsi tervektorisasi menghindari masalah ini dengan berjalan pada loop C yang rapat.
Untuk penyegar tentang cara menulis kode Python yang idiomatis dan efisien, lihat:
- 5 Tip untuk Menulis Kode Pandas yang Idiomatis - tutorial
- Writing Efficient Python Code - kursus
- Writing Efficient Code with pandas - kursus
Alternatif untuk reduce() di Python
Built-in:
- Kode C yang dioptimalkan. Built-in menjalankan loop mereka dalam kode C yang dioptimalkan, bukan Python. Itu menghindari overhead yang ditimbulkan
reduce(). Keunggulan kecepatan ini semakin terasa pada input besar. - Keterbacaan. Built-in memiliki nama deskriptif (
sum,min) sehingga maksudnya jelas. Panggilan kereduce()mengharuskan Anda membaca fungsi yang dilipat.
Loop:
- Kinerja. Secara umum loop berjalan lebih lambat daripada built-in tetapi lebih cepat daripada
reduce(). - Keterbacaan. Seperti built-in, loop biasanya lebih mudah dibaca daripada
reduce(). Panggilan kereduce()memaksa Anda mem-parsing pernyataan fungsional, sedangkan loop lebih Pythonic.
itertools.accumulate()
Pustaka itertools di Python menyediakan kumpulan iterator berkinerja tinggi seperti count(), product(), dan combinations(). Salah satu fungsi itertools yang berguna adalah itertools.accumulate(). Seperti reduce(), ia melipat sebuah fungsi pada iterable. Namun, accumulate() menyimpan nilai perantara dari komputasi, bukan hanya hasil akhirnya.
Sebagai contoh,
import itertools, operator
from functools import reduce
list(itertools.accumulate([1, 2, 3, 4], initial=0)) # [0, 1, 3, 6, 10]
reduce(operator.add, [1, 2, 3, 4], 0) # 10
Accumulate berguna saat Anda membutuhkan total berjalan atau nilai minimum/maksimum berjalan. Misalnya, Anda mungkin ingin mengetahui suhu maksimum dari bulan ke bulan.
Kesalahan Umum Saat Menggunakan reduce()
Saat Anda menggunakan reduce(), ingat jebakan berikut.
- Lebih suka alternatif yang lebih sederhana seperti built-in atau loop. Simpan
reduce()untuk saat Anda benar-benar membutuhkannya. - Tangani iterable kosong. Selalu gunakan initializer yang sesuai untuk menghindari error pada input kosong.
- Perhatikan masalah memori. Jangan memaksakan
reduce()ke situasi di mana pendekatan generator atau streaming akan lebih efisien. - Hindari lambda yang rumit. Gunakan fungsi dari modul
operatorbila memungkinkan. Lambda, khususnya dengan operasi non-asosiatif, dapat mengurangi kejelasan. - Utamakan kejelasan daripada kelihaian.
Praktik Terbaik dan Pedoman reduce() di Python
Seperti alat apa pun, ada praktik terbaik untuk reduce(). Jika Anda telah memutuskan reduce() adalah alat yang tepat, berikut beberapa pedoman penggunaannya.
Rancang reducer terlebih dahulu
- Definisikan kontrak dalam satu kalimat. "Gabungkan kunci kamus dengan menjumlahkan hitungan per kunci."
- Usahakan bersifat asosiatif jika memungkinkan. Ini memudahkan proses paralelisasi dan pengujian.
- Identifikasi elemen identitas Anda dan gunakan sebagai initializer. Misalnya, untuk penjumlahan, identitasnya adalah 0. Untuk
min, adalahmath.inf, dan untukset union, adalahset(). Ini membuat kode tangguh dan bebas dariTypeError.
Jaga tetap sederhana dan mudah dibaca
- Satu operasi yang jelas. Tanpa efek samping.
- Beri nama deskriptif pada reducer.
Dokumentasikan
- Di docstring, catat elemen identitas dan perilaku input kosong, asumsi asosiativitas, kebijakan error.
Uji
- Uji unit pada kasus tepi: iterable kosong, tipe campuran, nilai ekstrem.
- Uji asosiativitas.
Pantau kinerja
- Benchmark input kecil dan besar. Bandingkan benchmark dengan built-in dan loop.
- Jika kecepatan penting, pertimbangkan prapemrosesan, batching, dan memindahkan perhitungan berat ke NumPy atau pandas.
Aplikasi Lanjutan dan Kasus Penggunaan Nyata untuk reduce() di Python
Melihat kekurangannya, mungkin tampak bahwa reduce() tidak memiliki nilai nyata. Sebaliknya, reduce() memiliki banyak kasus penggunaan yang bermanfaat.
- Memproses struktur bersarang
- Operasi bergaya basis data
- Pipa pemrosesan data
- Aplikasi MapReduce
Memproses struktur bersarang
Reduce menyediakan cara yang rapi untuk menelusuri struktur data bersarang, seperti objek JSON, dengan mem-fold urutan kunci menjadi lookup berturut-turut.
import json
from functools import reduce
import operator
data = json.loads('''
{
"user": {
"id": "ABC123",
"name": "Alice",
"email": "alice@example.com",
"profile": {
"address": {
"city": "San Francisco",
"zip": "94103"
},
"age": 34,
"skills": ["Python", "Data Science", "Machine Learning"]
}
}
}
''')
# Example lookups with reduce + operator.getitem
city = reduce(operator.getitem, ["user", "profile", "address", "city"], data)
print(city) # "San Francisco"
user_id = reduce(operator.getitem, ["user", "id"], data)
print(user_id) # "ABC123"
age = reduce(operator.getitem, ["user", "profile", "age"], data)
print(age) # 34
Menggunakan reduce() masuk akal di sini. JSON-nya bersarang dalam: user → profile → address → city. Alih-alih merangkai lookup secara manual, wakili jalur sebagai list kunci. Lalu gunakan reduce(operator.getitem, path, data) untuk menelusurinya. Ini menjaga kode tetap generik, mudah dibaca, dan dapat digunakan ulang.
Pipa pemrosesan data
Reduce dapat menggerakkan pipa pemrosesan data dengan meneruskan data melalui rangkaian transformasi. Setiap fungsi menangani satu langkah, dan pipa dihasilkan dari menerapkannya secara berurutan. Berikut pipa sederhana yang mempraproses string teks sebelum dimasukkan ke model NLP.
from functools import reduce
import re
# Define preprocessing steps
def strip_punctuation(s):
return re.sub(r"[^\w\s]", "", s)
def to_lower(s):
return s.lower()
def remove_stopwords(s):
stops = {"the", "is", "a", "of"}
return " ".join(word for word in s.split() if word not in stops)
def stem_words(s):
# trivial "stemmer": cut off 'ing'
return " ".join(word[:-3] if word.endswith("ing") else word for word in s.split())
pipeline = [
strip_punctuation,
to_lower,
remove_stopwords,
stem_words,
]
# Input data
text = "The quick brown fox is Jumping over a log."
# Apply pipeline with reduce
processed = reduce(lambda acc, f: f(acc), pipeline, text)
print(processed) # quick brown fox jump over log
Penanganan error dalam aplikasi kompleks
Mari kembali ke contoh JSON bersarang. Saat ini, pemanggilan langsung ke reduce(operator.getitem, …) akan melempar KeyError atau TypeError jika sebuah kunci hilang atau jika bertemu non-dict. Untuk membuat kode lebih aman, definisikan fungsi pembantu yang membungkus operator.getitem dalam blok try/except dan mengembalikan nilai default saat terjadi error.
Berikut salah satu kemungkinan untuk fungsi pembantu.
def deep_get(data, keys, default=None):
"""Traverse nested dicts/lists safely with reduce."""
try:
return reduce(operator.getitem, keys, data)
except (KeyError, IndexError, TypeError):
return default
Sekarang, ubah contoh lookup untuk menggunakan fungsi baru kita alih-alih reduce() yang tidak dibungkus
# Example lookups
city = deep_get(data, ["user", "profile", "address", "city"], default="Unknown City")
print(city) # "San Francisco"
user_id = deep_get(data, ["user", "id"], default="N/A")
print(user_id) # "ABC123"
age = deep_get(data, ["user", "profile", "age"], default="N/A")
print(age) # 34
# Example with missing key
phone = deep_get(data, ["user", "profile", "phone"], default="No phone")
print(phone) # "No phone"
Transformasi data multi-langkah dengan map() dan filter()
Anda dapat menggabungkan reduce() dengan alat fungsional lain, seperti map() dan filter(), untuk membangun transformasi data multi-langkah. Berikut pipa prapemrosesan NLP kita sebelumnya yang ditulis secara fungsional.
from functools import reduce
import re
# Define preprocessing steps
def strip_punctuation(s):
return re.sub(r"[^\w\s]", "", s)
def to_lower(s):
return " ".join(map(str.lower, s.split()))
def remove_stopwords(s):
stops = {"the", "is", "a", "of"}
return " ".join(filter(lambda w: w not in stops, s.split()))
def stem_words(s):
# trivial "stemmer": cut off 'ing'
return " ".join(map(lambda w: w[:-3] if w.endswith("ing") else w, s.split()))
# Pipeline of transformations
pipeline = [
strip_punctuation,
to_lower,
remove_stopwords,
stem_words,
]
# Input data
text = "The quick brown fox is Jumping over a log."
# Apply pipeline with reduce
processed = reduce(lambda acc, f: f(acc), pipeline, text)
print(processed) # quick brown fox jump over log
Transformasinya adalah:
- Hapus tanda baca. "The quick brown fox is Jumping over a log." → "The quick brown fox is Jumping over a log"
- Huruf kecil
"The quick brown fox is Jumping over a log" → "the quick brown fox is jumping over a log"
- Hapus stop words. "the quick brown fox is jumping over a log" → "quick brown fox jumping over log"
- Stem kata
"quick brown fox jumping log" → "quick brown fox jump log"
Untuk mengeksplorasi lebih lanjut gagasan pemrograman fungsional dan vektorisasi, kami merekomendasikan artikel DataCamp berikut.
- Python filter(): Keep What You Need - tutorial
- Groupby, split-apply-combine dan pandas - tutorial
- Pandas Apply Tutorial - tutorial
Integrasi dengan Ekosistem Python Modern
Untuk menggunakan reduce() dengan baik, akan membantu jika memahami bagaimana ia cocok dalam ekosistem Python modern. Mari telusuri bagaimana posisinya bersama NumPy dan pandas, bagaimana ia menopang sistem paralel dan terdistribusi, serta bagaimana interaksinya dengan tooling modern seperti analis statis.
numpy dan pandas
NumPy dan pandas bekerja dari kode C yang dioptimalkan, jadi jangan duplikasi fungsionalitasnya dengan reduce(). Namun, reduce() adalah pilihan yang baik untuk pipa dengan langkah-langkah dinamis. Misalnya, Anda mungkin menggabungkan banyak transformasi NumPy pada sebuah array.
from functools import reduce
import numpy as np
def standardize(x):
return (x - x.mean()) / (x.std() + 1e-9)
def clip(x):
return np.clip(x, 0, 1)
def log(x):
return np.log1p(x)
x = np.array([1, 500, 40.5, 100, 250.45])
funcs = [standardize, clip, log]
y = reduce(lambda a, f: f(a), funcs, x) # x is ndarray
Kerangka komputasi paralel dan terdistribusi
Reduce adalah inti dari kerangka komputasi paralel dan terdistribusi. Sistem-sistem ini bekerja dengan membagi dataset menjadi partisi, memproses partisi tersebut secara paralel, lalu menggabungkan hasil parsial menjadi satu jawaban. Langkah "combine" adalah reduksi.
Agar reduksi berjalan dengan benar di seluruh klaster, pastikan kondisi berikut terpenuhi.
- Asosiativitas. Operasi harus memberikan hasil yang sama terlepas dari pengelompokan. Ini memungkinkan hasil parsial digabungkan dalam urutan apa pun di seluruh jaringan.
- Elemen identitas. Operasi harus memiliki initializer yang tidak memengaruhi hasil akhir. Ini memastikan kebenaran saat partisi kosong atau berukuran tidak merata.
Jika properti ini tidak terpenuhi, reduksi menjadi lambat (karena tidak dapat diparalelkan dengan aman) atau tidak benar (karena hasil bergantung pada urutan evaluasi).
Proses ini membutuhkan operasi yang asosiatif dan identitas yang jelas. Jika tidak, Anda mendapatkan kode yang lambat atau hasil yang salah.
Alat analisis statis
Analyzer statis (seperti mypy, Pyright, ruff, atau bandit) adalah alat yang memeriksa kode tanpa mengeksekusinya. Alat ini menangkap bug, menegakkan aturan gaya, dan memeriksa kebenaran tipe.
Analyzer statis kesulitan dengan reduce(). Dalam proses folding, tipe akumulator bisa berbeda dari tipe elemen, dan inferensi tipe menjadi berantakan.
Pertimbangkan kode berikut.
from functools import reduce
def add_chars(acc, word):
acc.extend(word) # extend adds each character of the string
return acc
chars = reduce(add_chars, ["hi", "ok"], [])
print(chars) # ['h', 'i', 'o', 'k']
Meskipun kode ini berjalan baik, analyzer statis mungkin mengeluhkan beberapa hal.
- Tipe elemen dari initializer [] ambigu.
- Analyzer mungkin mengasumsikan tipe akumulator dan elemen harus cocok.
Untuk membuat kode lebih jelas bagi manusia dan analyzer, tambahkan type hint.
from functools import reduce
from typing import List
def add_chars(acc: List[str], word: str) -> List[str]:
acc.extend(word) # extend adds each character of the string
return acc
chars: List[str] = reduce(add_chars, ["hi", "ok"], [])
print(chars) # ['h', 'i', 'o', 'k']
Sekarang reducer secara eksplisit menunjukkan
- Akumulator adalah
List[str]. - Setiap elemen adalah
str. - Tipe nilai kembalian adalah
list[str].
Dengan hint ini, analyzer statis dapat memeriksa kode secara ketat.
Kesimpulan
Reduce berasal dari pemrograman fungsional, di mana melipat koleksi menjadi satu hasil adalah gagasan inti. Meskipun tidak lagi menjadi alat kelas satu di Python, ia tetap memiliki tempatnya. Saat Anda membutuhkan pipa yang fleksibel, fold kustom, atau operasi yang tidak secara mulus dipetakan ke fungsi yang ada, reduce() adalah alat yang kuat. Jika digunakan dengan hati-hati, ia terintegrasi dengan baik dengan ekosistem Python yang lebih luas dan tetap menjadi alat yang praktis dalam situasi yang tepat.
Beberapa tautan Python terkait yang mungkin berguna.
Python reduce() FAQs
Apa yang dilakukan reduce()?
Fungsi ini berulang kali menerapkan fungsi dua argumen pada sebuah iterable untuk mereduksinya menjadi satu hasil.
Di mana reduce() didefinisikan?
Sebelum versi 3, ini adalah fungsi built-in. Sejak itu, fungsi ini berada di modul functools.
Fungsi seperti apa yang sebaiknya saya berikan ke reduce()?
Fungsi yang sederhana, asosiatif, dan terdokumentasi dengan baik. Hindari efek samping dan logika non-asosiatif.
Bagaimana perbandingannya dengan itertools.accumulate()?
reduce() hanya mengembalikan hasil akhir, accumulate() menghasilkan semua hasil perantara.
Kapan saya harus menggunakan initializer?
Gunakan initializer ketika iterable mungkin kosong.
