map, filter, dan reduce

Perbandingan map, filter, dan reduce dengan comprehension secara jujur.

Daftar isi

Tiga fungsi meliputi kebutuhan pemrosesan urutan secara fungsional, yaitu map untuk mengubah setiap elemen, filter untuk menyaring elemen yang memenuhi predikat, dan reduce untuk melipat seluruh urutan menjadi satu nilai. Ketiganya bekerja atas iterator sebagaimana protokol iterasi, dan seluruhnya dapat ditulis ulang sebagai comprehension atau perulangan biasa. Topik ini membahas perilaku ketiganya, lalu membandingkannya secara jujur dengan comprehension supaya pilihannya dapat dipertanggungjawabkan.

map menerapkan fungsi pada setiap elemen#

Fungsi map(fungsi, iterable) memanggil fungsi yang diberikan untuk setiap elemen dan menghasilkan nilai kembaliannya dalam bentuk iterator. Hasilnya bukan list, sehingga pencetakan langsung tidak menampilkan isinya, dan konversi lewat list() diperlukan ketika seluruh hasil ingin dilihat sekaligus. Argumen pertamanya boleh berupa fungsi buatan sendiri maupun fungsi bawaan yang sudah tersedia.

python Contoh: map dengan fungsi dan metode
def kuadrat(n):
    return n * n

angka = [1, 2, 3, 4]
hasil = map(kuadrat, angka)
print(type(hasil).__name__)
print(list(hasil))
kata = ["satu", "dua", "tiga"]
print(list(map(str.upper, kata)))
text Keluaran
map
[1, 4, 9, 16]
['SATU', 'DUA', 'TIGA']

Cetakan map menegaskan bahwa hasilnya berupa objek iterator, dan list() pada baris berikutnya menghabiskannya menjadi daftar utuh. Contoh kedua menunjukkan keunggulan map, yaitu meneruskan fungsi yang sudah ada seperti str.upper tanpa perlu menulis lambda sama sekali. Apabila fungsinya perlu ditulis terlebih dahulu hanya untuk pemanggilan ini, keunggulan itu mulai memudar, dan pembandingannya ada pada bagian akhir topik.

Fungsi ini menerima lebih dari satu iterable bila fungsinya menuntut argumen sebanyak itu, misalnya map(lambda a, b: a + b, nilai_a, nilai_b) menjumlahkan dua daftar sejajar elemen demi elemen. Sifat berhenti pada urutan terpendek berlaku di sini sebagaimana perilaku zip. Karena hasilnya iterator sekali pakai, konversi ke list diperlukan setiap kali datanya perlu dibaca kembali.

filter menyaring dengan predikat#

Fungsi filter(predikat, iterable) menghasilkan elemen yang lulus uji, yaitu elemen yang pemanggilan predikatnya mengembalikan nilai benar. Predikat berupa fungsi yang mengembalikan nilai benar atau salah, sehingga bentuk lambda yang pendek sering dipakai di posisi ini. Hasilnya juga berupa iterator yang bersifat lazy, dan elemen yang tidak lulus uji tidak memunculkan None maupun tanda apa pun.

python Contoh: filter dengan predikat
nilai = [78, 92, 65, 88, 55]
lulus = filter(lambda n: n >= 70, nilai)
print(list(lulus))
text Keluaran
[78, 92, 88]

Hanya nilai 78, 92, dan 88 yang lolos karena memenuhi syarat minimal 70, sedangkan sisanya ditinggalkan. Predikat tidak harus berupa lambda, dan fungsi bawaan pun sah dipakai pada posisi itu, misalnya str.islower atas deretan string. Bentuk khusus filter(None, data) mengembalikan elemen yang bernilai benar saja tanpa fungsi apa pun, meskipun predikat yang eksplisit biasanya lebih mudah dibaca. Ketika map dan filter dirangkai, data bergerak satu elemen demi satu elemen, yaitu elemen disaring lalu diubah, tanpa list perantara yang menampung hasil setiap tahap.

reduce melipat urutan menjadi satu nilai#

Fungsi reduce(fungsi, iterable, awal) dari modul functools mengerjakan urutan dari kiri ke kanan dengan satu akumulasi, yaitu hasil sementara yang diperbarui setiap langkah. Pada langkah pertama, akumulasi diisi nilai awal atau elemen pertama bila awal tidak diberikan, lalu fungsi dipanggil dengan akumulasi dan elemen berikutnya sampai urutannya selesai. Nama lain dari operasi ini adalah fold, karena urutan nilai dilipat menjadi satu hasil.

python Contoh: reduce dengan dan tanpa nilai awal
from functools import reduce

def kali(a, b):
    return a * b

print(reduce(kali, [1, 2, 3, 4]))
print(reduce(kali, [1, 2, 3, 4], 10))
print(reduce(lambda a, b: a + b, [10, 20, 30], 100))
text Keluaran
24
240
160

Perkalian tanpa nilai awal menghasilkan 24, sedangkan dengan nilai awal 10 hasilnya menjadi 240 karena akumulasinya memulai dari angka itu. Baris ketiga menunjukkan bentuk lambda untuk penjumlahan dengan nilai awal 100. Kebutuhan seperti menjumlah, mencari maksimum, atau menguji keberadaan sebaiknya memakai sum(), max(), dan any() yang sudah tersedia, sehingga reduce menyisakan pemakaian untuk pelipatan yang tidak punya padanan bawaan.

Perbandingan dengan comprehension#

Segala kerja map dan filter dapat ditulis ulang sebagai comprehension, dan hasilnya identik. Pembandingan berikut memeriksa kesetaraannya secara langsung, yaitu map(abs, ...) terhadap comprehension yang menghitung nilai mutlak dan filter terhadap comprehension bersyarat.

python Contoh: kesetaraan dengan comprehension
angka = [-4, 3, -1, 2, -5]
print(list(map(abs, angka)) == [abs(n) for n in angka])
print(list(filter(lambda n: n > 0, angka)) == [n for n in angka if n > 0])
text Keluaran
True
True

Kesetaraan itu berlaku juga untuk rangkaian yang lebih panjang. Dua bentuk berikut menyaring kelipatan tiga lalu mengubahnya menjadi string, dan keduanya menghasilkan nilai yang sama, meskipun membacanya menuntut usaha yang berbeda.

python Contoh: rangkaian map dan filter dibanding comprehension
hasil_map_filter = list(map(str, filter(lambda n: n % 3 == 0, range(1, 11))))
hasil_comprehension = [str(n) for n in range(1, 11) if n % 3 == 0]
print(hasil_map_filter)
print(hasil_map_filter == hasil_comprehension)
text Keluaran
['3', '6', '9']
True
Kebutuhanmap / filter / reducecomprehension
Mengubah elemen dengan fungsi yang sudah adamap(str.upper, kata)[s.upper() for s in kata]
Mengubah elemen dengan ekspresi singkatmap(lambda n: n * 2, data)[n * 2 for n in data]
Menyaring dengan predikatfilter(predikat, data)[n for n in data if predikat(n)]
Melipat menjadi satu nilaireduce(fungsi, data)tidak ada padanan satu baris

Tabel di atas menunjukkan keputusan yang lazim diambil komunitas Python, yaitu comprehension lebih mudah dibaca ketika logikanya berupa ekspresi yang ditulis langsung, karena alurnya terbaca dari kiri ke kanan tanpa fungsi bantu. Fungsi map tetap unggul ketika fungsinya sudah tersedia dan namanya mewakili maksudnya, sedangkan reduce menangani pelipatan yang tidak memiliki padanan comprehension. Hasil map dan filter bersifat lazy sehingga pertimbangan memorinya setara dengan generator ekspresi, sedangkan reduce menghabiskan seluruh urutannya untuk menghasilkan satu nilai.

Kesalahan umum#

Kesalahan pertama adalah menganggap hasil map sebagai list yang dapat dipakai berulang. Objeknya iterator yang habis setelah dikonsumsi, sehingga konversi kedua menghasilkan daftar kosong tanpa error apa pun.

python Contoh: map yang sudah dikonsumsi
angka = [1, 2, 3]
m = map(str, angka)
print(list(m))
print(list(m))
text Keluaran
['1', '2', '3']
[]

Kesalahan kedua adalah memanggil reduce tanpa nilai awal atas urutan yang mungkin kosong. Tanpa awal, fungsi ini menarik elemen pertama sebagai akumulasi, dan pada urutan kosong langkah itu tidak mungkin sehingga error TypeError muncul.

python Contoh: reduce atas urutan kosong
from functools import reduce

try:
    reduce(lambda a, b: a + b, [])
except TypeError as e:
    print(type(e).__name__, str(e))
text Keluaran
TypeError reduce() of empty iterable with no initial value

Perbaikannya adalah menyediakan nilai awal yang netral, misalnya 0 untuk penjumlahan dan 1 untuk perkalian, sehingga urutan kosong menghasilkan nilai netral itu juga. Kesalahan ketiga terjadi pada sisi keterbacaan, yaitu memaksakan map dengan lambda panjang padahal satu baris comprehension menyatakan maksudnya lebih jelas. Aturan yang sama juga membatasi reduce, karena pelipatan yang rumit lebih jelas ditulis sebagai perulangan dengan variabel akumulasi yang diberi nama.

Ringkasan#

  • map menerapkan fungsi pada setiap elemen dan filter menyaring elemen yang lulus predikat, keduanya menghasilkan iterator lazy.
  • reduce dari modul functools melipat urutan menjadi satu nilai melalui akumulasi, dengan nilai awal untuk keadaan kosong.
  • Semua hasil map dan filter dapat ditulis ulang sebagai comprehension yang identik keluarannya.
  • Comprehension lebih mudah dibaca untuk ekspresi yang ditulis langsung, sedangkan map unggul untuk fungsi yang sudah ada dan reduce untuk pelipatan tanpa padanan.

Referensi#