Mục lục bài họcĐang ở d04-b2
← AP Computer Science A
0/16 bài đã học xong
Chương 4 · Unit 4 — Data Collections · Bài 2/4 của chương · bài 14/16 của AP Computer Science A

ArrayList and Safe Removal

ArrayList và bẫy xoá khi đang duyệt
← Mục lục bài học
Lý thuyết · English

The six methods the exam tests

An ArrayList<E> grows and shrinks at run time. The type parameter must be an object type, so a list of integers is declared ArrayList<Integer> list = new ArrayList<Integer>(); — never ArrayList<int>.

int  size()             number of elements
E    get(int i)         element at index i
E    set(int i, E x)    replaces element i, RETURNS the old one
boolean add(E x)        appends at the end, returns true
void add(int i, E x)   inserts at i, shifts everything right
E    remove(int i)      removes element i, RETURNS it, shifts left

Two of these are quietly important. set and remove return the element that used to be there, which lets a question hide a value in an expression such as System.out.println(list.remove(0));.

Autoboxing

list.add(5) works on an ArrayList<Integer> because Java boxes the int into an Integer. Going the other way, int n = list.get(0); unboxes automatically.

Giải thích tiếng Việt

Sáu phương thức đề thi hỏi đi hỏi lại: size(), get(i), set(i, x), add(x), add(i, x), remove(i). Kiểu trong ngoặc nhọn bắt buộc là kiểu đối tượng: viết ArrayList<Integer> chứ không bao giờ ArrayList<int>. Với chuỗi thì là ArrayList<String>.

Hai phương thức trả về giá trị cũ. set(i, x)remove(i) đều trả về đúng phần tử vừa bị thay hoặc vừa bị lấy đi. Đề rất hay giấu một giá trị vào đó, kiểu System.out.println(list.remove(0)); — người đọc lướt tưởng dòng đó chỉ xoá, thực ra nó còn in ra thứ vừa xoá.

Hai phương thức làm dịch chỗ. add(i, x) đẩy mọi phần tử từ vị trí i trở đi sang phải một bậc; remove(i) kéo mọi phần tử phía sau tụt xuống trái một bậc. Sau mỗi lần gọi, chỉ số của các phần tử phía sau đã đổisize() cũng đổi. Đây chính là nguồn gốc của bẫy lớn nhất chương này.

Đóng hộp tự động. list.add(5) chạy được trên ArrayList<Integer> vì Java tự bọc int thành Integer; chiều ngược lại int n = list.get(0); cũng tự mở hộp.

list.remove(1) — mọi phần tử phía sau tụt xuống MỘT bậc "A" "B" "B" "C" 0 1 2 3 "A" "B" "C" 0 1 2 i vừa xong bậc 1, i++ nhảy sang 2 "B" thứ hai vừa tụt vào bậc 1 và KHÔNG bao giờ được xét
Vì sao xoá làm bỏ sót. Xoá phần tử ở bậc 1 xong, phần tử vốn ở bậc 2 tụt xuống bậc 1 — nhưng vòng lặp đã làm xong bậc 1 rồi, i++ đưa nó thẳng sang bậc 2. Phần tử vừa tụt xuống không bao giờ được xét. Hậu quả rất đặc trưng: dãy có hai giá trị cần xoá nằm cạnh nhau thì cái thứ hai sống sót, còn dãy không có giá trị nào kề nhau thì chương trình chạy đúng — nên lỗi này qua được phần lớn ca thử viết vội.
Ví dụ — xoá đúng cách hai lối

Cho ArrayList<Integer> list. Xoá mọi phần tử bằng 4. Viết hai lời giải đúng và chỉ ra chỗ khác nhau với bản sai.

Giải.

Bản sai — duyệt xuôi và vẫn tăng i sau khi xoá:

for (int i = 0; i < list.size(); i++) {
    if (list.get(i) == 4) { list.remove(i); }   // bỏ sót
}

Lối 1 — duyệt xuôi, chỉ tăng chỉ số khi KHÔNG xoá:

int i = 0;
while (i < list.size()) {
    if (list.get(i) == 4) {
        list.remove(i);        // không tăng i
    } else {
        i++;
    }
}

Lối 2 — duyệt ngược từ cuối về đầu:

for (int i = list.size() - 1; i >= 0; i--) {
    if (list.get(i) == 4) { list.remove(i); }
}

Lối 2 an toàn vì việc dồn chỗ chỉ ảnh hưởng tới các bậc phía sau vị trí vừa xoá, mà những bậc đó thì vòng lặp ngược đã đi qua rồi. Trong phòng thi lối 2 ngắn hơn và ít chỗ sai hơn.

Với [4, 4, 7, 4, 9]: bản sai cho [4, 7, 9], cả hai lối đúng đều cho [7, 9].

Bẫy hay mất điểm — Bẫy đắt nhất của chương: xoá phần tử trong lúc đang duyệt xuôi. Nó không ném ngoại lệ, không báo lỗi, chỉ âm thầm bỏ sót đúng những phần tử nằm liền kề nhau. Cách chặn có hai bước máy móc, làm là hết: một, hễ thân vòng lặp có remove thì cấm dùng for-each (nó ném ConcurrentModificationException) và cấm dùng for xuôi mặc định; hai, chọn một trong hai lối đã học — duyệt ngược, hoặc while chỉ tăng chỉ số ở nhánh else. Tự thử ca [4, 4] trước khi nộp là bắt được ngay.
Phải nhớ — setremove trả về giá trị cũ; add(i, x)remove(i) làm dịch chỗ và đổi size(). Muốn xoá khi đang duyệt thì duyệt ngược, hoặc chỉ tăng chỉ số khi không xoá.

Đọc xong rồi — làm thử ngay

Bài tập của chương Unit 4 — Data Collections gồm 14 câu trắc nghiệm và 8 đề tự luận. Đáp án hiện ngay khi chọn, miễn phí.

Làm bài tập chương →