Lab 04 cho người mới: quản lý danh sách sinh viên

Lab này gom nhiều kỹ năng về một mối: tách chuỗi họ tên, tính tuổi, sắp xếp 3 kiểu, tìm kiếm, xóa dồn mảng. Hai lớp: SV (một sinh viên) và DSSV (cả danh sách).

Bài 1. SV — tách họ tên và tính tuổi

Đề nói gì

Lớp SV lưu họ tên, ngày sinh, điểm. Tách được họ/đệm/tên, tính tuổi, xếp loại, chuẩn hóa tên, so sánh 2 sinh viên.

Từ mới

Ý tưởng

  1. tu(): cắt tên thành mảng từ (hàm phụ dùng chung).
  2. Họ = ô đầu, tên = ô cuối, đệm = nối các ô giữa.
  3. Tuổi = 2026 − năm sinh. Xếp loại theo bậc 8/7/5.

Code mổ xẻ: tách tên

private String[] tu() {
    String s = (hoTen == null) ? "" : hoTen.trim(); // null → rỗng, cắt cách thừa
    if (s.length() == 0) return new String[]{""};
    return s.split("\\s+");                          // cắt theo 1+ khoảng trắng
}
public String layTen() {
    String[] p = tu();
    return p[p.length - 1];                          // ô CUỐI là tên
}
public String layDem() {
    String[] p = tu();
    String dem = "";
    for (int i = 1; i < p.length - 1; i++) {         // chỉ các ô GIỮA
        dem += p[i];
        if (i < p.length - 2) dem += " ";            // chưa cuối thì thêm cách
    }
    return dem;
}

"\\s+" trong code Java nghĩa là regex \s+ (1+ khoảng trắng) — phải 2 gạch chéo vì gạch đầu là ký tự thoát của chuỗi. Tên 1 từ ("An"): đệm rỗng, họ = tên = "An", không lỗi.

Code mổ xẻ: chuẩn hóa tên

String tu = p[i].toLowerCase();                      // "vAN" → "van"
if (tu.length() > 0)
    tu = tu.substring(0, 1).toUpperCase() + tu.substring(1); // "Van"

Công thức viết hoa chữ đầu: hạ hết xuống thường, rồi thay chữ đầu bằng bản hoa của nó + phần còn lại. substring(1) là "từ vị trí 1 đến hết". Kiểm tra length() > 0 để chuỗi rỗng không nổ lỗi.

Chạy tay

"Nguyen Van An" → họ Nguyen, đệm Van, tên An. Khóa sắp xếp tên trong DSSV là tên + họ + đệm = "An Nguyen Van" — 2 bạn trùng tên An thì so tiếp họ. Ngày sinh 03/15/2005 → tuổi 2026 − 2005 = 21, ĐTB 8.5 → Giỏi. In ra: Nguyen Van An | 15/3/2005 | DTB 8.5 | Gioi | 21 tuoi.

Bẫy hay gặp

Tự luyện

  1. Thêm tenVietTat(): "Nguyen Van An" → "NVA".
  2. Thêm cungTuoi(SV sv): bằng tuổi nhau thì true.

Bài 2. DSSV — thêm, sắp xếp, tìm, xóa

Đề nói gì

DSSV quản lý tối đa N sinh viên: thêm (chặn null/đầy), hiển thị, sắp xếp theo điểm/tên/tuổi, tìm theo tên, liệt kê theo xếp loại, xóa theo tên, tuổi trung bình, cộng điểm cho người sinh trước 1980.

Từ mới

Ý tưởng

  1. them: null hoặc đầy → false; gán vào ds[soSV], tăng đếm.
  2. Ba sắp xếp = một khuôn nổi bọt, khác mỗi cái so.
  3. timTen: duyệt, khớp (không phân biệt hoa thường) thì trả object.
  4. xoa: tìm thấy thì dồn mảng, gán null ô thừa, giảm đếm.

Code mổ xẻ: ba kiểu so trong một khuôn

if (ds[j].layDTB() < ds[j + 1].layDTB())  // điểm: sau lớn hơn thì đổi (giảm dần)
if (khoaTen(ds[j]).compareTo(khoaTen(ds[j + 1])) > 0) // tên: so chuỗi
if (ds[j].layTuoi() > ds[j + 1].layTuoi()) // tuổi: sau nhỏ hơn thì đổi (tăng dần)

compareTo trả số dương khi chuỗi đứng sau theo alphabet. khoaTen ghép tên + họ + đệm để "An" luôn đứng trước "Bình" bất kể họ gì. Muốn đổi chiều sắp xếp thì đảo dấu so — 1 ký tự.

Code mổ xẻ: xóa dồn mảng

for (int i = k; i < soSV - 1; i++) ds[i] = ds[i + 1]; // kéo lên lấp ô k
ds[soSV - 1] = null;  // ô cuối thừa ra: gán null cho sạch
soSV--;               // giảm số lượng thật
return true;

Gán null ô cuối rất quan trọng: không gán thì 2 ô cùng trỏ 1 object, hiển thị ra 2 lần. Vòng dồn chạy tới soSV - 1 để ds[i+1] không vượt mảng.

Chạy tay cả chương trình

5 bạn: An 8.5, Bình 7.2, Nam 6.8, Hoa 9.1, Hùng 4.5. Sắp theo tên: An, Bình, Hoa, Hùng, Nam (theo tên gọi). Liệt kê Giỏi (≥8): An và Hoa. Tìm "Nam": thấy bạn Nam. Xóa "Hoa": mảng còn 4, Ket qua xoa: true. Tuổi trung bình 4 người còn lại ra 28.0 (nhờ bạn Hùng 48 tuổi kéo lên). Cộng 0.5 cho sinh trước 1980: Hùng 4.5 → 5.0, đổi từ Yếu thành Trung bình — đúng dòng cuối output mẫu DTB 5.0 | Trung binh | 48 tuoi.

Bẫy hay gặp

Tự luyện

  1. Thêm xoaTatCa(String ten): xóa mọi bạn trùng tên, trả về số đã xóa.
  2. Thêm treNhat(): trả về SV ít tuổi nhất (mẫu giữ-max ở Lab 02).

Phụ lục OOP: mảng đựng tham chiếu, không đựng object

Vẽ đúng cái mảng đang có

new SV[10] + thêm 5 bạn cho ra:

ds: [ref-An][ref-Bình][ref-Nam][ref-Hoa][ref-Hùng][null][null][null][null][null]
soSV = 5

Mảng đựng tham chiếu (địa chỉ), object nằm rải rác chỗ khác. soSV là ranh giới sống/chết: từ soSV trở đi là null, mọi vòng lặp dừng ở soSV là vì thế.

Sắp xếp chỉ đổi địa chỉ, object đứng yên

Nổi bọt trong sapHoten() đổi ds[j] với ds[j+1] — tức là đổi tờ giấy địa chỉ, 5 object SV không hề nhúc nhích, không có thuộc tính nào bị sửa. Hiểu vậy thì rõ vì sao sắp xếp xong timTen/lietKe vẫn đúng: object nguyên vẹn, chỉ thứ tự địa chỉ đổi. tmp kiểu SV cũng chỉ là tờ giấy tạm.

Xóa là quên địa chỉ + dồn giấy lên

Dồn mảng (ds[i] = ds[i+1]) là chép địa chỉ đè lên nhau, ds[soSV-1] = null là xé tờ giấy thừa. Object bị xóa không ai trỏ tới nữa — Java tự dọn (garbage collection), mình khỏi free như C. xoa("Hoa") xong, object Hoa vẫn còn đó nhưng không đường nào tới được, coi như biến mất khỏi danh sách.

Gửi thông điệp theo dây chuyền

dssv.lietKeXepLoai("Gioi") → với từng ds[i] gọi layXepLoai() → từng SV tự so dtb của mình. dssv không biết ngưỡng 8/7/5, SV không biết danh sách dài bao nhiêu. Mỗi lớp một việc — muốn đổi ngưỡng Giỏi chỉ sửa SV.layXepLoai().