Topik ini membahas data dua dimensi dan pola container bersarang, yaitu container yang elemennya berupa container lain. Pembahasannya mencakup std::vector<std::vector<int>> untuk matriks nilai ujian, pembentukan baris dan kolom secara dinamis, serta variasi container di dalam container untuk data berkelompok. Setelah membaca topik ini, data tabel seperti daftar nilai dan jadwal piket sudah dapat dimodelkan tanpa array manual.
Matriks dengan vector di dalam vector#
Sebuah matriks dapat dimodelkan sebagai vector yang setiap elemennya adalah satu baris berupa vector juga. Deklarasinya ditulis std::vector<std::vector<int>>, dan isinya dapat ditetapkan sejak awal dengan daftar inisialisasi bersarang, yaitu daftar yang setiap butirnya berisi satu baris. Akses satu sel memakai dua pasang kurung siku, sehingga nilai[baris][kolom] membaca sel pada baris bernomor baris dan kolom bernomor kolom.
#include <iostream>
#include <vector>
int main() {
std::vector<std::vector<int>> nilai = {
{80, 75, 90, 85},
{70, 65, 80, 75},
{90, 95, 85, 88}
};
for (std::size_t baris = 0; baris < nilai.size(); ++baris) {
int jumlah = 0;
for (std::size_t kolom = 0; kolom < nilai[baris].size(); ++kolom) {
std::cout << nilai[baris][kolom] << ' ';
jumlah += nilai[baris][kolom];
}
double rata = static_cast<double>(jumlah) / nilai[baris].size();
std::cout << "| rata-rata " << rata << '\n';
}
return 0;
}80 75 90 85 | rata-rata 82.5
70 65 80 75 | rata-rata 72.5
90 95 85 88 | rata-rata 89.5Ukuran nilai.size() menyatakan banyaknya baris, sedangkan nilai[baris].size() menyatakan banyaknya kolom pada baris tersebut. Perulangan luar bergerak menyusuri baris, sedangkan perulangan dalam menyusuri kolom pada satu baris, dan pola bersarang inilah pola tetap untuk memproses data dua dimensi. Tipe std::size_t dipakai karena itulah tipe kembalian dari size(), sehingga perbandingan pada kondisi perulangan tidak memunculkan peringatan ketidakcocokan tanda.
Perulangan bersarang juga dapat ditulis dengan range-based for, yaitu for (const auto& baris : nilai) pada lapisan luar dan for (int n : baris) pada lapisan dalam. Bentuk ini lebih pendek, tetapi posisi baris dan kolomnya tidak terlihat, sehingga perhitungan yang memerlukan posisi tetap lebih nyaman ditulis dengan indeks. Keduanya mengunjungi seluruh sel dengan hasil yang sama, dan setiap baris tetap merupakan vector yang berdiri sendiri.
Membangun matriks secara dinamis#
Apabila ukurannya baru diketahui saat program berjalan, matriks dibangun melalui constructor vector dengan dua argumen, yaitu banyaknya baris dan bentuk barisnya. Ekspresi std::vector<std::vector<int>>(baris, std::vector<int>(kolom)) menghasilkan baris buah baris, dan setiap baris berisi kolom buah nilai 0. Bentuk ini penting karena vector kosong yang baru dibuat tidak memiliki sel untuk diisi lewat indeks.
#include <iostream>
#include <vector>
int main() {
int baris = 3;
int kolom = 4;
std::vector<std::vector<int>> matriks(baris, std::vector<int>(kolom));
matriks[0][0] = 1;
matriks[1][2] = 7;
matriks[2][3] = 5;
std::cout << "Banyak baris: " << matriks.size() << '\n';
std::cout << "Banyak kolom baris pertama: " << matriks[0].size() << '\n';
std::cout << "Nilai matriks[1][2]: " << matriks[1][2] << '\n';
return 0;
}Banyak baris: 3
Banyak kolom baris pertama: 4
Nilai matriks[1][2]: 7Setiap sel yang tidak diisi tetap bernilai 0 karena constructor mengisi barisnya dengan nilai bawaan int. Nilai pengisiannya juga dapat ditentukan, misalnya std::vector<int>(kolom, 0) yang eksplisit mengisi 0, atau nilai lain sesuai kebutuhan. Apabila pemeriksaan batas diinginkan, aksesnya ditulis berlapis sebagai matriks.at(1).at(2), dan setiap lapisan melempar std::out_of_range apabila indeksnya di luar batas. Pola constructor ini juga menjadi perlindungan terhadap kesalahan yang dibahas pada bagian akhir topik.
Baris-baris pada model ini disimpan sebagai vector yang berdiri sendiri, sehingga memorinya tidak wajib berurutan dalam satu blok tunggal seperti array dua dimensi bergaya C. Konsekuensinya, ukuran setiap baris dapat berubah secara independen lewat push_back() pada baris tersebut. Fleksibilitas ini berbeda dari std::array dua dimensi yang seluruh dimensinya tetap sejak deklarasi.
Container di dalam container untuk data berkelompok#
Pola bersarang tidak terbatas pada matriks angka. Container di dalam container sangat cocok untuk data yang terkelompok secara alami, misalnya jadwal piket yang setiap harinya memuat beberapa nama. Pada contoh berikut, setiap baris berisi nama hari sebagai elemen pertama dan nama-nama petugas setelahnya, sehingga jumlah petugas per hari tidak harus sama.
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::vector<std::string>> piket = {
{"Senin", "Ali", "Budi"},
{"Selasa", "Citra", "Dewi"},
{"Rabu", "Ali", "Citra"}
};
for (std::size_t i = 0; i < piket.size(); ++i) {
std::cout << "Piket " << piket[i].front() << ": ";
for (std::size_t j = 1; j < piket[i].size(); ++j) {
std::cout << piket[i][j];
if (j + 1 < piket[i].size()) {
std::cout << ", ";
}
}
std::cout << '\n';
}
return 0;
}Piket Senin: Ali, Budi
Piket Selasa: Citra, Dewi
Piket Rabu: Ali, CitraBerbeda dari array dua dimensi yang setiap barisnya harus sama panjang, baris-baris di sini bebas memiliki ukuran berbeda, karena setiap baris adalah vector yang berdiri sendiri. Perulangan dalam pun memulai indeksnya dari 1 untuk melompati nama hari, dan kondisi j + 1 < piket[i].size() menjaga koma tidak tercetak setelah nama terakhir. Pola ini berlaku umum untuk data berkelompok lain, misalnya daftar belanja per hari atau nilai per mata pelajaran.
Penyusunan bersarang dapat berlanjut ke tingkat yang lebih dalam, misalnya tiga lapis untuk data tiga dimensi, dan aturannya tetap sama. Dalam praktiknya, kedalaman lebih dari dua lapis membuat kode sulit dibaca, sehingga struktur semacam itu biasanya ditulis ulang sebagai tipe bernama setelah struct dipelajari. Untuk kebutuhan umum, dua lapis sudah mencakup sebagian besar data tabel yang ditemui.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul pada container bersarang.
- Mengisi sel pada vector yang barisnya masih kosong. Deklarasi
std::vector<std::vector<int>> matriks;menghasilkan nol baris, dan deklarasi dengan satu angka sepertistd::vector<std::vector<int>> matriks(3);menghasilkan tiga baris kosong, sehinggamatriks[0][0] = 1;berujung pada undefined behavior. Bangun barisnya lebih dahulu dengan constructor dua argumen. - Beranggapan setiap baris berukuran sama. Baris container bersarang bebas berbeda panjang, sehingga
nilai[0].size()tidak menjamin nilai yang sama pada baris lain. Baca ukuran setiap baris dari baris itu sendiri, seperti yang dilakukan perulangan dalam pada contoh matriks. - Menukar urutan indeks. Ekspresi
nilai[baris][kolom]membaca baris lebih dahulu, sedangkan penulisan terbalik tidak menghasilkan error bila ukuran kedua dimensinya sama, melainkan nilai yang salah. Beri nama variabel indeks yang jelas untuk menahan kebingungan ini.
Ketiga kesalahan tersebut berpangkal pada sifat yang sama, yaitu setiap baris adalah vector mandiri yang ukurannya disimpan pada baris itu sendiri. Kebiasaan yang melindungi dari semuanya adalah membangun baris terlebih dahulu sebelum mengisi sel, kemudian membaca ukuran dari baris yang sedang diproses, bukan dari baris pertama. Dengan dua kebiasaan itu, container bersarang menjadi sama teratur dengan container satu dimensi.
Ringkasan#
- Matriks dimodelkan sebagai vector yang elemennya vector baris, dengan akses
[baris][kolom], banyaknya baris padasize()luar, dan banyaknya kolom padasize()dalam. - Constructor dua argumen
std::vector<std::vector<int>>(baris, std::vector<int>(kolom))membangun matriks bernilai nol yang ukurannya baru diketahui saat program berjalan. - Pola container di dalam container juga melayani data berkelompok dengan baris yang bebas panjangnya, misalnya jadwal piket per hari.
Referensi#
- std::vector — cppreference.com — Dokumentasi std::vector beserta constructor dan member function-nya.
- std::vector::at — cppreference.com — Akses dengan pemeriksaan batas beserta exception yang dilempar.
- Declaration of an array — cppreference.com — Kaidah array multidimensi sebagai pembanding model bersarang.
- Multidimensional std::array — LearnCpp — Model data dua dimensi berukuran tetap pada std::array.