List (Array) #
Array adalah tulang punggung manipulasi data di Ruby. Hampir setiap program yang nyata berurusan dengan koleksi data — daftar pengguna, baris transaksi, hasil query database, baris file teks — dan semuanya bermuara ke Array. Yang membuat Array Ruby istimewa bukan sekadar kemampuan menyimpan data, tapi ekosistem method Enumerable yang sangat kaya: lebih dari 60 method untuk transformasi, filter, pengelompokan, pencarian, dan akumulasi yang bisa di-chain satu sama lain. Memahami Array Ruby secara mendalam berarti memahami cara berpikir fungsional yang idiomatik — mengekspresikan “apa yang ingin dicapai” bukan “bagaimana cara melakukannya langkah demi langkah”.
Membuat Array #
Ada beberapa cara membuat Array di Ruby, masing-masing punya kegunaan yang berbeda:
# Literal — cara paling umum
kosong = []
angka = [1, 2, 3, 4, 5]
nama = ["Rina", "Budi", "Citra"]
campur = [1, "dua", :tiga, 4.0, nil, true]
# %w — shorthand untuk array string tanpa tanda kutip
buah = %w[apel mangga jeruk nanas durian]
# => ["apel", "mangga", "jeruk", "nanas", "durian"]
# %i — shorthand untuk array symbol
status = %i[aktif nonaktif pending diblokir]
# => [:aktif, :nonaktif, :pending, :diblokir]
# Array.new — dengan ukuran dan nilai default
nol = Array.new(5, 0) # => [0, 0, 0, 0, 0]
kosong_str = Array.new(3, "") # => ["", "", ""]
# Array.new dengan blok — nilai berbeda per elemen
kuadrat = Array.new(6) { |i| i ** 2 }
# => [0, 1, 4, 9, 16, 25]
indeks = Array.new(5) { |i| i + 1 }
# => [1, 2, 3, 4, 5]
# Konversi dari tipe lain
dari_range = (1..10).to_a # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
dari_hash = {a: 1, b: 2}.to_a # => [[:a, 1], [:b, 2]]
dari_string = "halo".chars # => ["h", "a", "l", "o"]
Hati-hati denganArray.new(n, objek)— semua elemen merujuk ke objek yang sama di memori. Jika objeknya mutable (seperti Array atau Hash), mengubah satu elemen akan mengubah semua elemen. Gunakan blokArray.new(n) { [] }untuk memastikan setiap elemen adalah objek yang berbeda.
# ANTI-PATTERN: semua elemen array berbagi objek yang sama
baris = Array.new(3, [])
baris[0] << "a"
puts baris.inspect # => [["a"], ["a"], ["a"]] ← semua berubah!
# BENAR: blok membuat objek baru setiap iterasi
baris = Array.new(3) { [] }
baris[0] << "a"
puts baris.inspect # => [["a"], [], []] ← hanya [0] yang berubah
Mengakses Elemen #
Ruby menyediakan cara yang sangat fleksibel untuk mengakses elemen — dari indeks tunggal hingga slice dengan range:
arr = ["a", "b", "c", "d", "e", "f"]
# Indeks tunggal
puts arr[0] # => "a" (pertama)
puts arr[-1] # => "f" (terakhir)
puts arr[-2] # => "e" (kedua dari belakang)
# Slice dengan range
puts arr[1..3].inspect # => ["b", "c", "d"] (inklusif)
puts arr[1...3].inspect # => ["b", "c"] (eksklusif)
puts arr[2..].inspect # => ["c", "d", "e", "f"] (sampai akhir)
puts arr[..2].inspect # => ["a", "b", "c"] (dari awal)
# Slice dengan (mulai, panjang)
puts arr[2, 3].inspect # => ["c", "d", "e"] (mulai indeks 2, ambil 3)
# Method yang lebih ekspresif
puts arr.first.inspect # => "a"
puts arr.last.inspect # => "f"
puts arr.first(3).inspect # => ["a", "b", "c"]
puts arr.last(2).inspect # => ["e", "f"]
puts arr.sample # => elemen acak
puts arr.sample(2).inspect # => 2 elemen acak
# Indeks di luar batas — tidak error, kembalikan nil
puts arr[100].inspect # => nil
puts arr.fetch(100) # => IndexError!
puts arr.fetch(100, "default") # => "default"
dig — Akses Elemen Array Bersarang #
Untuk array multidimensi, dig mengakses elemen bersarang tanpa risiko NoMethodError jika salah satu level nil:
data = [
[1, [2, 3]],
[4, [5, 6]],
[7, [8, 9]]
]
puts data[1][1][0] # => 5
puts data.dig(1, 1, 0) # => 5 (lebih aman)
puts data.dig(5, 1, 0) # => nil (tidak crash jika indeks tidak ada)
puts data[5][1][0] # => NoMethodError: nil[][1] (crash!)
Menambah dan Menghapus Elemen #
arr = [1, 2, 3]
# Menambah di belakang
arr.push(4) # => [1, 2, 3, 4]
arr << 5 # => [1, 2, 3, 4, 5] (paling idiomatik)
arr.append(6, 7) # => [1, 2, 3, 4, 5, 6, 7]
# Menambah di depan
arr.unshift(0) # => [0, 1, 2, 3, 4, 5, 6, 7]
arr.prepend(-1, -2) # => [-2, -1, 0, 1, 2, 3, 4, 5, 6, 7]
# Menghapus dari belakang dan depan
akhir = arr.pop # hapus dan kembalikan elemen terakhir
depan = arr.shift # hapus dan kembalikan elemen pertama
# Menghapus berdasarkan nilai
arr = [1, 2, 3, 2, 4, 2]
arr.delete(2) # hapus SEMUA elemen bernilai 2 => [1, 3, 4]
# Menghapus berdasarkan indeks
arr = [10, 20, 30, 40, 50]
arr.delete_at(2) # hapus indeks 2 => [10, 20, 40, 50]
# Menghapus berdasarkan kondisi (destruktif)
arr = [1, 2, 3, 4, 5, 6]
arr.delete_if { |n| n.even? } # => [1, 3, 5]
arr.keep_if { |n| n.odd? } # => sama, tapi lebih ekspresif
# Sisipkan di posisi tertentu
arr = [1, 2, 4, 5]
arr.insert(2, 3) # sisipkan 3 di indeks 2 => [1, 2, 3, 4, 5]
# Ganti range elemen
arr = [1, 2, 3, 4, 5]
arr[1..2] = [20, 30] # => [1, 20, 30, 4, 5]
arr[1, 2] = [200] # => [1, 200, 4, 5] (ganti 2 elemen mulai indeks 1)
Transformasi — map, flat_map, zip, product #
map — Ubah Setiap Elemen #
harga = [15_000_000, 350_000, 3_500_000, 450_000]
# Terapkan diskon 10%
setelah_diskon = harga.map { |h| (h * 0.9).round }
puts setelah_diskon.inspect
# => [13500000, 315000, 3150000, 405000]
# Transformasi ke objek lain
pengguna = [
{ nama: "Rina", skor: 85 },
{ nama: "Budi", skor: 92 },
{ nama: "Citra", skor: 78 }
]
nama_saja = pengguna.map { |p| p[:nama] }
# => ["Rina", "Budi", "Citra"]
dengan_grade = pengguna.map do |p|
grade = p[:skor] >= 90 ? "A" : p[:skor] >= 80 ? "B" : "C"
p.merge(grade: grade)
end
flat_map — Map lalu Ratakan #
kalimat = ["ruby itu menyenangkan", "belajar setiap hari"]
# map biasa menghasilkan array of arrays
kalimat.map { |k| k.split }
# => [["ruby", "itu", "menyenangkan"], ["belajar", "setiap", "hari"]]
# flat_map meratakan satu tingkat
semua_kata = kalimat.flat_map { |k| k.split }
# => ["ruby", "itu", "menyenangkan", "belajar", "setiap", "hari"]
# Contoh nyata: kumpulkan semua tag dari banyak artikel
artikel = [
{ judul: "Ruby", tag: %i[ruby pemula] },
{ judul: "Rails", tag: %i[ruby rails web] },
{ judul: "Python", tag: %i[python tips] }
]
semua_tag = artikel.flat_map { |a| a[:tag] }.uniq
puts semua_tag.inspect
# => [:ruby, :pemula, :rails, :web, :python, :tips]
zip — Gabungkan Array Paralel #
zip menggabungkan array berdasarkan posisi elemen:
nama = ["Rina", "Budi", "Citra"]
nilai = [85, 92, 78]
kota = ["Bandung", "Jakarta", "Surabaya"]
gabungan = nama.zip(nilai, kota)
puts gabungan.inspect
# => [["Rina", 85, "Bandung"], ["Budi", 92, "Jakarta"], ["Citra", 78, "Surabaya"]]
# Sangat berguna untuk membangun Hash
profil = nama.zip(nilai).to_h
puts profil.inspect
# => {"Rina"=>85, "Budi"=>92, "Citra"=>78}
product — Semua Kombinasi #
product menghasilkan semua kombinasi elemen dari dua atau lebih array — produk kartesian:
warna = [:merah, :biru]
ukuran = [:S, :M, :L]
varian = warna.product(ukuran)
puts varian.inspect
# => [[:merah, :S], [:merah, :M], [:merah, :L],
# [:biru, :S], [:biru, :M], [:biru, :L]]
# Berguna untuk membuat semua kombinasi opsi produk
puts warna.product(ukuran).length # => 6 varian
Filter dan Pencarian #
data = [3, 1, 7, 2, 9, 4, 8, 5, 6]
# select / filter — elemen yang memenuhi kondisi
puts data.select { |n| n > 5 }.inspect # => [7, 9, 8, 6]
puts data.filter { |n| n.odd? }.inspect # => [3, 1, 7, 9, 5] (alias select)
# reject — elemen yang TIDAK memenuhi kondisi
puts data.reject { |n| n > 5 }.inspect # => [3, 1, 2, 4, 5]
# find / detect — elemen pertama yang memenuhi kondisi
puts data.find { |n| n > 5 } # => 7
puts data.detect { |n| n > 5 } # => 7 (alias)
# find_index — indeks elemen pertama yang memenuhi kondisi
puts data.find_index { |n| n > 5 } # => 2
puts data.index(9) # => 4 (cari nilai tertentu)
# filter_map — filter DAN transform sekaligus (Ruby 2.7+)
# ANTI-PATTERN: dua langkah terpisah
hasil = data.select { |n| n > 5 }.map { |n| n * 10 }
# BENAR: filter_map — satu langkah, lebih efisien
hasil = data.filter_map { |n| n * 10 if n > 5 }
puts hasil.inspect # => [70, 90, 80, 60]
# Contoh nyata filter_map
pengguna = [
{ nama: "Rina", aktif: true, skor: 85 },
{ nama: "Budi", aktif: false, skor: 92 },
{ nama: "Citra", aktif: true, skor: 78 },
{ nama: "Deni", aktif: true, skor: 60 }
]
nama_aktif_skor_tinggi = pengguna.filter_map do |p|
p[:nama] if p[:aktif] && p[:skor] >= 75
end
puts nama_aktif_skor_tinggi.inspect # => ["Rina", "Citra"]
Operasi Himpunan #
Array Ruby mendukung operasi himpunan matematika secara langsung melalui operator:
a = [1, 2, 3, 4, 5]
b = [3, 4, 5, 6, 7]
puts (a | b).inspect # => [1, 2, 3, 4, 5, 6, 7] — union (gabungan, unik)
puts (a & b).inspect # => [3, 4, 5] — intersection (irisan)
puts (a - b).inspect # => [1, 2] — difference (selisih)
puts (a + b).inspect # => [1, 2, 3, 4, 5, 3, 4, 5, 6, 7] — concat (boleh duplikat)
# Contoh nyata: cari pengguna yang ada di kedua sistem
sistem_a = ["user1", "user2", "user3", "user4"]
sistem_b = ["user2", "user4", "user5", "user6"]
di_keduanya = sistem_a & sistem_b # => ["user2", "user4"]
hanya_di_a = sistem_a - sistem_b # => ["user1", "user3"]
hanya_di_b = sistem_b - sistem_a # => ["user5", "user6"]
semua_gabungan = sistem_a | sistem_b # => semua tanpa duplikat
Pengurutan #
angka = [5, 2, 8, 1, 9, 3]
puts angka.sort.inspect # => [1, 2, 3, 5, 8, 9] (ascending)
puts angka.sort.reverse.inspect # => [9, 8, 5, 3, 2, 1] (descending)
# sort_by — lebih idiomatik untuk objek kompleks
produk = [
{ nama: "Monitor", harga: 3_500_000 },
{ nama: "Mouse", harga: 350_000 },
{ nama: "Laptop", harga: 15_000_000 },
{ nama: "Keyboard", harga: 450_000 }
]
# Urutkan berdasarkan harga ascending
produk.sort_by { |p| p[:harga] }.each { |p| puts "#{p[:nama]}: #{p[:harga]}" }
# => Mouse: 350000, Keyboard: 450000, Monitor: 3500000, Laptop: 15000000
# Urutkan berdasarkan harga descending
produk.sort_by { |p| -p[:harga] }.map { |p| p[:nama] }
# => ["Laptop", "Monitor", "Keyboard", "Mouse"]
# Urutkan dengan beberapa kriteria — nama ascending jika harga sama
orang = [
{ nama: "Citra", umur: 28 },
{ nama: "Andi", umur: 35 },
{ nama: "Budi", umur: 28 },
]
orang.sort_by { |p| [p[:umur], p[:nama]] }
# => [{Citra,28}, {Budi,28}, {Andi,35}] — umur asc, nama asc jika umur sama
Pengelompokan dan Agregasi #
transaksi = [
{ bulan: "Jan", jumlah: 100_000 },
{ bulan: "Jan", jumlah: 50_000 },
{ bulan: "Feb", jumlah: 200_000 },
{ bulan: "Feb", jumlah: 75_000 },
{ bulan: "Mar", jumlah: 150_000 }
]
# group_by — kelompokkan berdasarkan kriteria
per_bulan = transaksi.group_by { |t| t[:bulan] }
per_bulan.each do |bulan, data|
total = data.sum { |t| t[:jumlah] }
puts "#{bulan}: Rp #{total} (#{data.length} transaksi)"
end
# => Jan: Rp 150000 (2 transaksi)
# => Feb: Rp 275000 (2 transaksi)
# => Mar: Rp 150000 (1 transaksi)
# tally — hitung frekuensi kemunculan
warna_favorit = [:biru, :merah, :biru, :hijau, :biru, :merah]
puts warna_favorit.tally.inspect
# => {:biru=>3, :merah=>2, :hijau=>1}
# partition — pisah menjadi dua grup
angka = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
genap, ganjil = angka.partition { |n| n.even? }
puts genap.inspect # => [2, 4, 6, 8, 10]
puts ganjil.inspect # => [1, 3, 5, 7, 9]
# each_slice dan each_cons — iterasi dalam potongan
(1..10).each_slice(3) { |slice| print "#{slice} " }
# => [1, 2, 3] [4, 5, 6] [7, 8, 9] [10]
(1..5).each_cons(3) { |cons| print "#{cons} " }
# => [1, 2, 3] [2, 3, 4] [3, 4, 5] (jendela geser)
Array Multidimensi dan Flatten #
matriks = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# Akses elemen
puts matriks[1][2] # => 6
puts matriks.dig(2, 1) # => 8
# Iterasi matriks
matriks.each_with_index do |baris, i|
baris.each_with_index do |nilai, j|
print "#{nilai} "
end
puts
end
# Transpose — tukar baris dan kolom
puts matriks.transpose.inspect
# => [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# flatten — ratakan array bersarang
sarang = [1, [2, 3], [4, [5, [6, 7]]]]
puts sarang.flatten.inspect # => [1, 2, 3, 4, 5, 6, 7] (semua level)
puts sarang.flatten(1).inspect # => [1, 2, 3, 4, [5, [6, 7]]] (satu level)
puts sarang.flatten(2).inspect # => [1, 2, 3, 4, 5, [6, 7]] (dua level)
Destructuring Array #
Ruby mendukung destructuring — membongkar array menjadi variabel-variabel individual:
# Destructuring dasar
a, b, c = [1, 2, 3]
puts "#{a}, #{b}, #{c}" # => 1, 2, 3
# Dengan splat — tangkap "sisa"
pertama, *sisanya = [1, 2, 3, 4, 5]
puts pertama.inspect # => 1
puts sisanya.inspect # => [2, 3, 4, 5]
*awal, terakhir = [1, 2, 3, 4, 5]
puts awal.inspect # => [1, 2, 3, 4]
puts terakhir # => 5
kepala, *tengah, ekor = [1, 2, 3, 4, 5]
puts kepala # => 1
puts tengah.inspect # => [2, 3, 4]
puts ekor # => 5
# Swap tanpa variabel sementara
x, y = 10, 20
x, y = y, x
puts "x=#{x}, y=#{y}" # => x=20, y=10
# Destructuring dalam iterasi
pasangan = [["Rina", 85], ["Budi", 92], ["Citra", 78]]
pasangan.each do |(nama, nilai)|
puts "#{nama}: #{nilai}"
end
# Atau tanpa tanda kurung
pasangan.each do |nama, nilai|
puts "#{nama}: #{nilai}"
end
Method Pengecekan dan Utilitas #
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# Pengecekan kondisi
puts arr.any? { |n| n > 8 } # => true
puts arr.all? { |n| n > 0 } # => true
puts arr.none? { |n| n > 10 } # => true
puts arr.one? { |n| n > 8 } # => true (tepat satu)
# Statistik
puts arr.min # => 1
puts arr.max # => 9
puts arr.sum # => 31
puts arr.count # => 8
puts arr.count(1) # => 2 (berapa kali nilai 1 muncul)
puts arr.count { |n| n > 4 } # => 3
# Deduplikasi
duplikat = [1, 2, 2, 3, 3, 3, 4]
puts duplikat.uniq.inspect # => [1, 2, 3, 4]
puts duplikat.uniq.length # => 4
# compact — hapus semua nil
dengan_nil = [1, nil, 2, nil, 3, nil]
puts dengan_nil.compact.inspect # => [1, 2, 3]
# rotate — putar array
arr = [1, 2, 3, 4, 5]
puts arr.rotate.inspect # => [2, 3, 4, 5, 1] (putar 1 ke kiri)
puts arr.rotate(2).inspect # => [3, 4, 5, 1, 2]
puts arr.rotate(-1).inspect # => [5, 1, 2, 3, 4] (putar ke kanan)
# combination dan permutation — kombinasi matematika
puts [1, 2, 3].combination(2).to_a.inspect
# => [[1, 2], [1, 3], [2, 3]]
puts [1, 2, 3].permutation(2).to_a.inspect
# => [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
Method Destruktif vs Non-destruktif #
Ruby mengikuti konvensi: method berakhiran ! memodifikasi array asli, sedangkan yang tanpa ! mengembalikan array baru:
| Method | Non-destruktif | Destruktif |
|---|---|---|
| sort | arr.sort | arr.sort! |
| reverse | arr.reverse | arr.reverse! |
| flatten | arr.flatten | arr.flatten! |
| compact | arr.compact | arr.compact! |
| uniq | arr.uniq | arr.uniq! |
| map | arr.map | arr.map! |
| select | arr.select | arr.select! |
| reject | arr.reject | arr.reject! |
# ANTI-PATTERN: menggunakan ! sembarangan
data = [3, 1, 2]
data.sort! # memodifikasi data asli — mungkin tidak disengaja
# BENAR: non-destruktif untuk keamanan
hasil = data.sort # data tetap [3, 1, 2]
puts hasil.inspect # => [1, 2, 3]
puts data.inspect # => [3, 1, 2] (tidak berubah)
# ! berguna ketika memang ingin modifikasi in-place dan tidak perlu variabel baru
buffer = []
1000.times { buffer << rand(100) }
buffer.sort!.uniq! # modifikasi in-place — hemat alokasi objek baru
Chaining yang Ekspresif #
Kekuatan sesungguhnya Array Ruby muncul saat kamu menggabungkan method menjadi pipeline yang terbaca seperti deskripsi masalahnya:
transaksi = [
{ id: 1, pengguna: "Rina", jumlah: 150_000, valid: true },
{ id: 2, pengguna: "Budi", jumlah: 50_000, valid: false },
{ id: 3, pengguna: "Rina", jumlah: 200_000, valid: true },
{ id: 4, pengguna: "Citra", jumlah: 75_000, valid: true },
{ id: 5, pengguna: "Budi", jumlah: 300_000, valid: true },
]
# "Siapa saja pengguna unik yang punya transaksi valid di atas 100rb?"
pengguna_aktif = transaksi
.select { |t| t[:valid] && t[:jumlah] > 100_000 }
.map { |t| t[:pengguna] }
.uniq
.sort
puts pengguna_aktif.inspect # => ["Budi", "Rina"]
# "Berapa total transaksi valid per pengguna?"
total_per_pengguna = transaksi
.select { |t| t[:valid] }
.group_by { |t| t[:pengguna] }
.transform_values { |data| data.sum { |t| t[:jumlah] } }
.sort_by { |_, total| -total }
.to_h
total_per_pengguna.each do |pengguna, total|
puts "#{pengguna}: Rp #{total}"
end
# => Budi: Rp 300000
# => Rina: Rp 350000 ← Rina punya dua transaksi valid
# => Citra: Rp 75000
Ringkasan #
%w[]dan%i[]adalah shorthand ringkas untuk array string dan symbol — gunakan daripada menulis tanda kutip satu per satu.Array.new(n) { [] }bukanArray.new(n, [])— blok memastikan setiap elemen adalah objek yang berbeda, menghindari shared reference.diguntuk array bersarang — lebih aman dari chaining[][]karena mengembalikannilalih-alih crash ketika ada level yangnil.filter_map=select+mapdalam satu langkah — lebih efisien dan lebih ringkas untuk filter sekaligus transformasi (Ruby 2.7+).- Operasi himpunan
|,&,-langsung tersedia — tidak perlu loop manual untuk union, irisan, atau selisih dua array.sort_bylebih idiomatik darisortdengan spaceship — terutama untuk objek kompleks, dan mendukung pengurutan multi-kriteria dengan array.zipuntuk menggabungkan array paralel — sangat berguna untuk membangun Hash dari dua array atau menggabungkan data dari sumber berbeda.flatten(n)untuk kontrol kedalaman —flattentanpa argumen meratakan semua level,flatten(1)hanya satu level.each_slicedaneach_consuntuk iterasi dalam potongan atau jendela geser.- Method destruktif
!hanya saat memang perlu modifikasi in-place — secara default, gunakan yang non-destruktif untuk mencegah efek samping yang tidak disengaja.