Array

Array dengan batas yang menjadi bagian tipenya serta pemotongan slice.

Daftar isi

Array adalah tipe komposit yang menyimpan deretan elemen bertipe sama, dan setiap elemen diakses melalui indeks. Pada Ada, batas indeks ditetapkan pada definisi tipe untuk array terkendala, atau pada deklarasi setiap objek untuk array tak terkendala. Topik ini membahas definisi tipe array terkendala dan tak terkendala, atribut batas, agregat array, serta pemotongan slice, yaitu pengambilan sebagian array yang diperlakukan sebagai array utuh. Contoh yang digunakan adalah deret bacaan sensor per jam selama satu hari.

Batas array sebagai bagian tipe#

Tipe array dapat didefinisikan dalam dua bentuk. Bentuk terkendala menyebut batasnya langsung, misalnya type Matriks_3 is array (1 .. 3, 1 .. 3) of Float; untuk array dua dimensi dengan batas tetap. Bentuk tak terkendala menuliskan range <> pada indeksnya, misalnya type Bacaan_Jam is array (Integer range <>) of Integer;, dan batasnya baru ditetapkan pada deklarasi setiap objek. Pendekatan tak terkendala membuat satu tipe dapat digunakan untuk objek dengan panjang berbeda, sedangkan pemeriksaan batas tetap dijalankan terhadap batas masing-masing objek.

ada Contoh: definisi tipe array tak terkendala
-- tidak-dijalankan
type Bacaan_Jam is array (Integer range <>) of Integer;

Riwayat  : Bacaan_Jam (0 .. 23);   -- batas ditetapkan pada objek
Cadangan : Bacaan_Jam (0 .. 47);

Indeks array tidak harus dimulai dari nol atau satu. Indeksnya boleh bertipe enumerasi atau subtipe rentang apa pun, misalnya array (Hari) of Boolean untuk menyimpan penanda kehadiran per hari. Ukuran array objek terkendala diketahui pada saat kompilasi, sehingga penyimpanannya dapat disiapkan tanpa alokasi dinamis. Dengan batas yang menyertai tipe dan objeknya, setiap akses indeks dapat diperiksa terhadap batas tersebut.

Array dua dimensi dideklarasikan dengan dua rentang yang dipisahkan koma, dan setiap rentang memiliki atribut batas dengan penomoran dimensi, misalnya 'First (2) untuk indeks pertama pada dimensi kedua. Pemotongan slice hanya berlaku pada array satu dimensi, sedangkan array multidimensi diakses per elemen atau melalui perulangan bersarang. Pemilihan bentuk array mengikuti bentuk datanya, yaitu deret bacaan sepanjang waktu cocok dengan array satu dimensi, sedangkan tabel baris dan kolom cocok dengan array dua dimensi.

Atribut batas dan agregat array#

Atribut batas membaca keadaan array tanpa menyebut angka batasnya secara langsung. Atribut 'First menyatakan indeks pertama, 'Last indeks terakhir, 'Length banyaknya elemen, dan 'Range digunakan pada perulangan untuk menelusuri seluruh indeks. Penggunaan atribut ini menjaga program tetap benar apabila batas array ditinjau ulang, karena tidak ada angka batas yang ditulis ulang pada perulangan. Apabila batas objek diperluas, perulangan yang menggunakan 'Range tidak perlu diubah sama sekali.

Nilai awal array disusun dengan agregat array, dan bentuk penulisannya mengikuti agregat record. Bentuk posisional menuliskan nilai berurutan sesuai indeks, sedangkan bentuk bernama menyebut indeks dengan tanda =>. Beberapa indeks dapat diisi sekaligus dengan notasi 3 | 4 => 15, dan others menutup seluruh indeks yang tersisa. Agregat yang menyebut indeks tidak wajib lengkap selama others menutup sisanya, sehingga nilai awal dapat ditulis ringkas untuk array berukuran besar. Tanda <> juga sah pada agregat array dengan makna yang sama, yaitu elemen yang bersangkutan mengambil nilai bawaan tipe elemennya.

Pemotongan slice#

Slice mengambil bagian array yang berurutan dan memperlakukan hasilnya sebagai array dengan batas dari rentang pemotongannya. Program berikut menyimpan bacaan sensor per jam, memotong jendela pagi sebagai array tersendiri, lalu menyalin isi antar slice dengan panjang yang sama.

ada jendela_bacaan.adb
with Ada.Text_IO; use Ada.Text_IO;

procedure Jendela_Bacaan is

   -- Tipe array tak terkendala: batasnya diisi pada deklarasi objek.
   type Bacaan_Jam is array (Integer range <>) of Integer;

   Riwayat : Bacaan_Jam (0 .. 23) :=
     (0 => 18, 1 => 17, 2 => 16, 3 | 4 => 15, others => 14);

   procedure Cetak (Label : String; A : Bacaan_Jam) is
   begin
      Put (Label & " [");
      for J in A'Range loop
         Put (Integer'Image (A (J)));
         if J /= A'Last then
            Put (",");
         end if;
      end loop;
      Put_Line (" ]");
   end Cetak;

begin
   Put_Line ("Batas array: pertama" & Integer'Image (Riwayat'First)
             & ", terakhir" & Integer'Image (Riwayat'Last)
             & ", panjang" & Integer'Image (Riwayat'Length));
   Cetak ("Seluruh", Riwayat);

   -- Pemotongan slice: bagian array diperlakukan sebagai array utuh.
   declare
      Pagi : Bacaan_Jam (0 .. 5) := Riwayat (0 .. 5);
   begin
      Cetak ("Pagi   ", Pagi);
   end;

   -- Penugasan antar slice dengan batas indeks yang berbeda.
   Riwayat (0 .. 2) := Riwayat (21 .. 23);
   Cetak ("Setelah", Riwayat);
end Jendela_Bacaan;
bash
# kompilasi program
gnatmake -q jendela_bacaan.adb
# jalankan program
./jendela_bacaan
text Keluaran
Batas array: pertama 0, terakhir 23, panjang 24
Seluruh [ 18, 17, 16, 15, 15, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14 ]
Pagi    [ 18, 17, 16, 15, 15, 14 ]
Setelah [ 14, 14, 14, 15, 15, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14 ]

Ekspresi Riwayat (0 .. 5) memotong enam elemen pertama dan nilainya disalin ke Pagi pada deklarasinya, sehingga pengubahan Pagi setelahnya tidak berpengaruh pada Riwayat. Penugasan Riwayat (0 .. 2) := Riwayat (21 .. 23) menyalin tiga elemen terakhir ke awal array, dan keluaran menunjukkan tiga nilai pertama berubah menjadi 14 sedangkan sisanya tetap. Kedua slice pada penugasan tersebut memiliki batas indeks yang berbeda tetapi panjang yang sama, dan panjang yang sama merupakan syarat penugasan antar slice. Prosedur Cetak menerima slice tersebut sebagai parameter bertipe Bacaan_Jam biasa. Notasi pemotongan juga berlaku pada array dengan indeks enumerasi, misalnya Bacaan (Senin .. Rabu), dengan rentang yang mengikuti urutan nilai enumerasinya.

Pemeriksaan batas indeks#

Akses dengan indeks di luar batas objek membangkitkan Constraint_Error, dan pemotongan di luar batas array asal ditangkap pemeriksaan batas yang sama. Program berikut sengaja membaca indeks 24 pada array berbatas 0 sampai 23.

ada uji_batas.adb
with Ada.Text_IO; use Ada.Text_IO;

procedure Uji_Batas is
   type Bacaan_Jam is array (Integer range <>) of Integer;
   Riwayat : Bacaan_Jam (0 .. 23) := (others => 0);
   Jam     : Integer := 24;
begin
   Put_Line ("Bacaan jam ke-23:" & Integer'Image (Riwayat (23)));
   Put_Line ("Bacaan jam ke-24:" & Integer'Image (Riwayat (Jam)));
end Uji_Batas;
bash
# kompilasi program
gnatmake -q uji_batas.adb
# jalankan program
./uji_batas
text Keluaran
uji_batas.adb:9:60: warning: value not in range of subtype of "Standard.Integer" defined at line 5 [enabled by default]
uji_batas.adb:9:60: warning: Constraint_Error will be raised at run time [enabled by default]
Bacaan jam ke-23: 0

raised CONSTRAINT_ERROR : uji_batas.adb:9 range check failed

Dua baris peringatan muncul pada waktu kompilasi karena GNAT menelusuri nilai Jam yang tidak pernah berubah, sehingga pelanggaran batas sudah terlihat pada saat kompilasi. Pemeriksa run time tetap menegakkan batas tersebut, dan program berhenti dengan pesan raised CONSTRAINT_ERROR : uji_batas.adb:9 range check failed. Pesan menyebut pemeriksaan rentang karena tipe indeks array ini adalah Integer, dan pemeriksaan indeks dijalankan sebagai pemeriksaan rentang pada tipe indeksnya. Perbaikannya adalah mendeklarasikan Jam dengan subtipe yang rentangnya sesuai batas array, misalnya 0 .. 23, sehingga kesalahan yang sama tertangkap pada waktu kompilasi.

Pemeriksaan batas diaktifkan secara bawaan pada GNAT, sehingga tidak ada flag yang perlu diingat untuk menjalankannya. Apabila pemeriksaan perlu dimatikan pada bagian program yang menuntut kecepatan tinggi, penonaktifannya dilakukan secara selektif dengan pragma Suppress, dan keputusan semacam itu dicatat dengan jelas karena pemeriksaan indeks tidak lagi dijalankan pada bagian tersebut.

Kesalahan umum#

  • Memperlakukan 'Length sebagai indeks terakhir. Untuk array berbatas 1 .. 8, nilai 'Length dan 'Last sama-sama 8, tetapi untuk array berbatas 0 .. 23 keduanya berbeda. Perbaikannya, gunakan 'Last atau 'Range untuk penelusuran indeks.
  • Menugaskan dua slice dengan panjang yang berbeda. Pemeriksaan panjang dijalankan pada waktu berjalan, dan pelanggarannya membangkitkan Constraint_Error. Perbaikannya, samakan panjang kedua sisi atau susun perulangan manual apabila panjangnya memang berbeda.
  • Mengira seluruh array dimulai dari indeks nol atau satu. Batas array di Ada bebas berupa rentang skalar apa pun, sehingga asumsi tersebut menimbulkan error indeks. Perbaikannya, akses indeks melalui atribut 'First dan 'Last daripada angka yang ditulis langsung.
  • Mengira slice mengikat array asal secara permanen. Slice menyalin nilai pada penugasan, sehingga perubahan terhadap hasil pemotongan tidak berpengaruh pada array asal. Perbaikannya, gunakan penugasan langsung ke slice array asal apabila perubahan tersebut memang diinginkan.

Ringkasan#

  • Batas indeks menjadi bagian tipe array, ditetapkan pada definisi tipe untuk array terkendala atau pada deklarasi objek untuk array tak terkendala.
  • Atribut 'First, 'Last, 'Length, dan 'Range membaca batas array, sehingga perulangan tetap benar apabila batasnya berubah.
  • Slice memotong array berurutan dan menyalin nilai pada penugasan antar slice berpanjang sama, dengan pemeriksaan indeks yang tetap dijalankan pada waktu berjalan.

Referensi#