List

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 dengan Array.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 blok Array.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:

MethodNon-destruktifDestruktif
sortarr.sortarr.sort!
reversearr.reversearr.reverse!
flattenarr.flattenarr.flatten!
compactarr.compactarr.compact!
uniqarr.uniqarr.uniq!
maparr.maparr.map!
selectarr.selectarr.select!
rejectarr.rejectarr.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) { [] } bukan Array.new(n, []) — blok memastikan setiap elemen adalah objek yang berbeda, menghindari shared reference.
  • dig untuk array bersarang — lebih aman dari chaining [][] karena mengembalikan nil alih-alih crash ketika ada level yang nil.
  • filter_map = select + map dalam 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_by lebih idiomatik dari sort dengan spaceship — terutama untuk objek kompleks, dan mendukung pengurutan multi-kriteria dengan array.
  • zip untuk menggabungkan array paralel — sangat berguna untuk membangun Hash dari dua array atau menggabungkan data dari sumber berbeda.
  • flatten(n) untuk kontrol kedalamanflatten tanpa argumen meratakan semua level, flatten(1) hanya satu level.
  • each_slice dan each_cons untuk 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.

← Sebelumnya: Eksepsi   Berikutnya: Map →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact