Bài 18: Thực hành dữ liệu mảng một chiều và hai chiều

I. Ôn tập kiến thức nền

Trước khi bắt tay vào thực hành, cần nắm vững một số kiến thức đã học ở Bài 17:

Mảng một chiều được biểu diễn bằng kiểu list trong Python, các phần tử cùng kiểu và được truy cập qua chỉ số (bắt đầu từ 0). Dùng vòng for để duyệt, toán tử in để kiểm tra sự tồn tại của phần tử.

Mảng hai chiều là mô hình “danh sách trong danh sách” – mỗi phần tử của danh sách gốc lại là một danh sách con. Truy cập phần tử bằng hai chỉ số A[i][j] (hàng i, cột j). Duyệt toàn bộ bằng hai vòng for lồng nhau.

Các hàm và thao tác cần nhớ:

Thao tác Cú pháp Ý nghĩa
Nhập chuỗi rồi tách input().split() Tách chuỗi nhập thành danh sách các xâu
Chuyển kiểu hàng loạt [float(x) for x in ...] List comprehension – chuyển từng xâu thành số thực
Thêm phần tử list.append(x) Thêm x vào cuối danh sách
Độ dài danh sách len(list) Trả về số phần tử
Tổng các phần tử sum(list) Trả về tổng (chỉ dùng với list số)

II. Hướng dẫn thực hành

Nhiệm vụ 1: Quản lí điểm kiểm tra của một học sinh

Yêu cầu bài toán

Viết chương trình quản lí điểm kiểm tra một môn học của một học sinh trong một học kì, gồm hai phần: nhập các đầu điểm kiểm tra (từ hai đầu điểm trở lên), sau đó tính và in ra điểm trung bình, điểm thấp nhất, điểm cao nhất.

Phân tích hướng giải quyết

Bài toán này chỉ liên quan đến một dãy số (các đầu điểm của một học sinh), nên sử dụng mảng một chiều là phù hợp.

Luồng xử lí chính:

  • Bước 1 – Nhập liệu: Người dùng nhập tất cả điểm trên một dòng, các điểm cách nhau bởi dấu cách. Dùng split() để tách chuỗi thành danh sách các xâu, rồi chuyển từng xâu sang số thực bằng list comprehension.
  • Bước 2 – Khởi tạo: Gán biến total = 0 để cộng dồn, biến MinMax ban đầu bằng phần tử đầu tiên của mảng.
  • Bước 3 – Duyệt và tính toán: Dùng vòng for đi qua từng phần tử, cộng dồn vào total, đồng thời so sánh cập nhật MinMax.
  • Bước 4 – In kết quả: Điểm trung bình = total / len(marks), cùng với MinMax.

Chương trình mẫu

# qldiem.py – Quản lí điểm một học sinh

marks = []
line = input("Hãy nhập các điểm kiểm tra cách nhau bởi dấu cách: ")
marks = [float(x) for x in line.split()]

total = 0
Min = marks[0]
Max = marks[0]

for m in marks:
    total = total + m
    if Min > m:
        Min = m
    if Max < m:
        Max = m

print("Điểm trung bình:", total / len(marks))
print("Điểm cao nhất:", Max)
print("Điểm thấp nhất:", Min)

Giải thích chi tiết từng phần

Dòng 1–3 (Nhập liệu): marks khởi tạo là danh sách rỗng. Hàm input() đọc cả dòng nhập thành một chuỗi. Phương thức split() tách chuỗi tại mỗi dấu cách, tạo ra danh sách các xâu. List comprehension [float(x) for x in line.split()] chuyển từng xâu thành số thực và gán lại cho marks.

Ví dụ: Nếu người dùng nhập 8.5 7.0 9.5 6.0, thì marks = [8.5, 7.0, 9.5, 6.0].

Dòng 4–6 (Khởi tạo): total = 0 chuẩn bị cho việc cộng dồn. MinMax đều gán bằng phần tử đầu tiên marks[0] – đây là kỹ thuật chuẩn để tìm giá trị lớn nhất/nhỏ nhất: bắt đầu từ phần tử đầu, rồi so sánh lần lượt với các phần tử còn lại.

Dòng 7–12 (Duyệt và xử lí): Vòng for m in marks lấy lần lượt từng giá trị điểm gán vào biến m. Với mỗi giá trị: cộng m vào total; nếu m nhỏ hơn Min hiện tại thì cập nhật Min; nếu m lớn hơn Max hiện tại thì cập nhật Max.

Dòng 13–15 (In kết quả): Trung bình = tổng chia số phần tử. MinMax lúc này đã chứa giá trị nhỏ nhất và lớn nhất sau khi duyệt hết mảng.

Nhiệm vụ 2: Quản lí điểm kiểm tra của cả lớp

Yêu cầu bài toán

Viết chương trình quản lí điểm kiểm tra một môn học trong một học kì của tất cả học sinh trong lớp. Chương trình cần nhập tên và điểm từng học sinh, sau đó in ra điểm trung bình của mỗi người, tìm học sinh có điểm trung bình cao nhất và điểm kiểm tra thấp nhất trong toàn bộ dữ liệu.

Phân tích hướng giải quyết

Bây giờ ta quản lí điểm của nhiều học sinh, mỗi học sinh lại có nhiều đầu điểm. Đây chính là bài toán cần mảng hai chiều: mỗi hàng lưu các đầu điểm của một học sinh, mỗi cột tương ứng một đầu điểm cụ thể. Song song đó, cần thêm một mảng một chiều riêng để lưu tên học sinh.

Luồng xử lí chính:

  • Bước 1 – Nhập liệu: Dùng vòng lặp for chạy n lần (n là số học sinh). Mỗi lần lặp: nhập tên → thêm vào mảng names; nhập dãy điểm → chuyển thành list số thực → thêm vào mảng hai chiều marks_all.
  • Bước 2 – Khởi tạo biến thống kê: max_avg lưu điểm trung bình cao nhất (khởi tạo = 0), index_max_avg lưu chỉ số của học sinh đó, Min lưu điểm thấp nhất toàn bảng (khởi tạo = phần tử đầu tiên).
  • Bước 3 – Duyệt mảng hai chiều: Vòng for ngoài duyệt từng học sinh (từng hàng). Vòng for trong duyệt từng đầu điểm của học sinh đó (từng cột). Trong quá trình duyệt: cộng dồn tính tổng điểm mỗi hàng, cập nhật Min toàn bảng, tính trung bình từng hàng rồi so sánh với max_avg.
  • Bước 4 – In kết quả: In tên học sinh có trung bình cao nhất (dùng index_max_avg để truy ngược lại mảng names) và điểm thấp nhất.

Chương trình mẫu

# qldiem_ca_lop.py – Quản lí điểm cả lớp

names = []
marks_all = []
n = int(input("Hãy nhập số lượng học sinh: "))

for i in range(n):
    name = input("Tên học sinh thứ " + str(i + 1) + ": ")
    names.append(name)
    line = input("Điểm kiểm tra của học sinh thứ " + str(i + 1) + ": ")
    marks = [float(x) for x in line.split()]
    marks_all.append(marks)

# Định nghĩa các biến để thống kê
max_avg = 0
index_max_avg = 0
Min = marks_all[0][0]

# Duyệt qua mảng 2 chiều
for i in range(n):
    total = 0
    for j in range(len(marks_all[i])):
        total = total + marks_all[i][j]
        if Min > marks_all[i][j]:
            Min = marks_all[i][j]
    avg = total / len(marks_all[i])
    print("Điểm trung bình của", names[i], ":", avg)
    if max_avg < avg:
        max_avg = avg
        index_max_avg = i

# In ra kết quả
print(names[index_max_avg], "đạt điểm trung bình", max_avg, "cao nhất lớp.")
print("Điểm thấp nhất:", Min)

Giải thích chi tiết từng phần

Phần nhập liệu (dòng 1–11):

Hai mảng names (một chiều, chứa tên) và marks_all (hai chiều, chứa điểm) được khởi tạo rỗng. Vòng lặp chạy n lần, mỗi lần nhập tên và điểm của một học sinh. Tên được thêm vào names, dãy điểm sau khi chuyển đổi được thêm vào marks_all như một hàng mới.

Ví dụ sau khi nhập 3 học sinh:

names = ["Quang", "Hà", "Bình"]
marks_all = [[7.5, 8.0, 9.0],
             [8.5, 7.0, 8.0],
             [9.0, 9.5, 10.0]]

Phần khởi tạo biến thống kê (dòng 13–16):

max_avg = 0: Giá trị ban đầu nhỏ nhất có thể để bất kỳ trung bình nào cũng lớn hơn. index_max_avg = 0: Lưu vị trí học sinh có trung bình cao nhất. Min = marks_all[0][0]: Lấy điểm đầu tiên trong bảng làm mốc so sánh.

Phần duyệt mảng hai chiều (dòng 18–28):

Đây là phần cốt lõi. Với mỗi học sinh thứ i:

  • Biến total được đặt lại bằng 0 (vì mỗi học sinh tính tổng điểm riêng).
  • Vòng for trong duyệt marks_all[i][j] – tức từng điểm của học sinh đó. Mỗi điểm được cộng vào total và so sánh với Min toàn bảng.
  • Sau khi duyệt hết điểm của học sinh i, tính avg rồi in ra.
  • So sánh avg với max_avg: nếu lớn hơn thì cập nhật cả max_avgindex_max_avg.

Phần in kết quả (dòng 30–32):

Dùng index_max_avg làm chỉ số để lấy tên từ mảng names, kết hợp với max_avgMin đã tìm được.

Minh hoạ cấu trúc dữ liệu

Để dễ hình dung, cấu trúc marks_all có thể vẽ thành bảng:

Đầu điểm 1 Đầu điểm 2 Đầu điểm 3
HS 0 – Quang 7.5 8.0 9.0
HS 1 – Hà 8.5 7.0 8.0
HS 2 – Bình 9.0 9.5 10.0

Truy cập marks_all[1][2] → lấy điểm đầu thứ 3 của học sinh Hà → kết quả: 8.0. Mảng names chạy song song: names[1]"Hà".

III. Phương pháp giải bài tập SGK

Bài Luyện tập

1. Chỉnh sửa lại chương trình của Nhiệm vụ 1 để bổ sung chức năng:

a) Thông báo điểm đầu tiên và điểm cuối cùng trong danh sách.

b) Cho phép người dùng tra cứu đầu điểm thứ n với quy ước n bắt đầu từ 1 ứng với điểm đầu tiên. Nếu n lớn hơn tổng số đầu điểm hoặc nhỏ hơn 1, cần thông báo không hợp lệ và yêu cầu người dùng nhập lại.

Lời giải:

# Phần nhập liệu và tính toán giữ nguyên như Nhiệm vụ 1
marks = []
line = input("Hãy nhập các điểm kiểm tra cách nhau bởi dấu cách: ")
marks = [float(x) for x in line.split()]

total = 0
Min = marks[0]
Max = marks[0]
for m in marks:
    total = total + m
    if Min > m:
        Min = m
    if Max < m:
        Max = m

print("Điểm trung bình:", total / len(marks))
print("Điểm cao nhất:", Max)
print("Điểm thấp nhất:", Min)

# --- Phần bổ sung ---

# a) Điểm đầu tiên và cuối cùng
print("Điểm đầu tiên:", marks[0])
print("Điểm cuối cùng:", marks[len(marks) - 1])

# b) Tra cứu điểm thứ n
while True:
    n = int(input("Nhập số thứ tự đầu điểm cần tra cứu (bắt đầu từ 1): "))
    if 1 <= n <= len(marks):
        print(f"Đầu điểm thứ {n} là: {marks[n - 1]}")
        break
    else:
        print("Số thứ tự không hợp lệ! Vui lòng nhập lại.")

Giải thích:

Phần a: Điểm đầu tiên nằm ở chỉ số 0, điểm cuối cùng nằm ở chỉ số len(marks) - 1 (hoặc có thể dùng marks[-1] trong Python).

Phần b: Vì người dùng nhập n bắt đầu từ 1 nhưng chỉ số list bắt đầu từ 0, nên phần tử cần truy cập là marks[n - 1]. Vòng while True kết hợp break giúp lặp lại cho đến khi nhập hợp lệ.

2. Chỉnh sửa lại chương trình để người dùng có thể:

a) Tra cứu các đầu điểm kiểm tra theo STT (số thứ tự) của học sinh. Quy ước số thứ tự bắt đầu từ 1. Nếu người dùng nhập STT lớn hơn số lượng học sinh thì chương trình thông báo STT không hợp lệ và yêu cầu nhập lại.

b) Tra cứu điểm kiểm tra cụ thể lần thứ n của một học sinh theo STT. Nếu n và STT không hợp lệ chương trình cần thông báo và yêu cầu nhập lại.

Lời giải:

# Phần nhập liệu và thống kê giữ nguyên như Nhiệm vụ 2
names = []
marks_all = []
n = int(input("Hãy nhập số lượng học sinh: "))

for i in range(n):
    name = input("Tên học sinh thứ " + str(i + 1) + ": ")
    names.append(name)
    line = input("Điểm kiểm tra của học sinh thứ " + str(i + 1) + ": ")
    marks = [float(x) for x in line.split()]
    marks_all.append(marks)

max_avg = 0
index_max_avg = 0
Min = marks_all[0][0]

for i in range(n):
    total = 0
    for j in range(len(marks_all[i])):
        total = total + marks_all[i][j]
        if Min > marks_all[i][j]:
            Min = marks_all[i][j]
    avg = total / len(marks_all[i])
    print("Điểm trung bình của", names[i], ":", avg)
    if max_avg < avg:
        max_avg = avg
        index_max_avg = i

print(names[index_max_avg], "đạt điểm trung bình", max_avg, "cao nhất lớp.")
print("Điểm thấp nhất:", Min)

# --- Phần bổ sung ---

# a) Tra cứu điểm theo STT học sinh
while True:
    stt = int(input("\nNhập STT học sinh cần tra cứu (bắt đầu từ 1): "))
    if 1 <= stt <= n:
        print(f"Các đầu điểm của {names[stt - 1]}:", marks_all[stt - 1])
        break
    else:
        print("STT không hợp lệ! Vui lòng nhập lại.")

# b) Tra cứu điểm cụ thể lần thứ k của học sinh theo STT
while True:
    stt = int(input("\nNhập STT học sinh (bắt đầu từ 1): "))
    if 1 <= stt <= n:
        break
    print("STT không hợp lệ! Vui lòng nhập lại.")

while True:
    k = int(input("Nhập lần kiểm tra thứ mấy (bắt đầu từ 1): "))
    if 1 <= k <= len(marks_all[stt - 1]):
        print(f"Điểm lần {k} của {names[stt - 1]}: {marks_all[stt - 1][k - 1]}")
        break
    print("Lần kiểm tra không hợp lệ! Vui lòng nhập lại.")

Giải thích:

Phần a: STT người dùng nhập bắt đầu từ 1, trong khi chỉ số mảng bắt đầu từ 0, nên truy cập bằng marks_all[stt - 1]. Kiểm tra 1 <= stt <= n để đảm bảo hợp lệ.

Phần b: Cần kiểm tra tính hợp lệ cho cả STT học sinh và số thứ tự đầu điểm. Lưu ý mỗi học sinh có thể có số đầu điểm khác nhau, nên điều kiện kiểm tra là 1 <= k <= len(marks_all[stt - 1]). Truy cập điểm cụ thể bằng marks_all[stt - 1][k - 1] (hai lần trừ 1 vì cả hai đều quy ước từ 1).

Bài Vận dụng

Bài 1 (Vận dụng SGK)

Viết chương trình nhập vào từ bàn phím danh sách tên (không gồm họ và đệm) học sinh cách nhau bởi dấu cách và lưu vào trong một mảng. Giả thiết rằng tên không có khoảng trắng. Sau đó hãy thống kê xem có bao nhiêu tên khác nhau và mỗi tên xuất hiện bao nhiêu lần trong danh sách.

Lời giải:

line = input("Nhập danh sách tên cách nhau bởi dấu cách: ")
ds_ten = line.split()

da_dem = []
so_ten_khac_nhau = 0

for ten in ds_ten:
    if ten not in da_dem:
        so_ten_khac_nhau += 1
        print(f"Tên '{ten}' xuất hiện {ds_ten.count(ten)} lần")
        da_dem.append(ten)

print(f"\nTổng số tên khác nhau: {so_ten_khac_nhau}")

Giải thích: Danh sách da_dem đóng vai trò đánh dấu – mỗi tên chỉ được thống kê một lần. Khi gặp tên chưa nằm trong da_dem, ta dùng ds_ten.count(ten) để đếm số lần xuất hiện trong toàn bộ danh sách, đồng thời tăng biến so_ten_khac_nhau.

Bài 2 (Vận dụng SGK)

2Viết chương trình nhập từ bàn phím số tự nhiên m và n. Sau đó lần lượt nhập m dòng, mỗi dòng bao gồm n số cách nhau bởi dấu cách. Đưa dữ liệu đã nhập vào ma trận A, in ma trận A ra màn hình. Sau đó:

a) Tính tổng các phần tử ma trận A.

b) In ra dòng có tổng các phần tử lớn nhất (nếu có nhiều dòng bằng nhau thì in tất cả các dòng).

c) In ra giá trị các phần tử phân biệt trong ma trận tức là nếu có các giá trị xuất hiện nhiều lần trong ma trận A thì chỉ in ra một lần.

d) Cho phép người dùng tìm số lần xuất hiện của một số bất kì trong ma trận A, ví dụ người dùng nhập vào số 3, chương trình thông báo số 3 xuất hiện x lần trong ma trận tại các vị trí (i, j) cụ thể.

Lời giải:

# --- Nhập ma trận ---
m = int(input("Nhập số hàng m: "))
n = int(input("Nhập số cột n: "))
A = []
for i in range(m):
    dong = input(f"Nhập hàng {i + 1} ({n} số cách nhau bởi dấu cách): ")
    hang = [int(x) for x in dong.split()]
    A.append(hang)

# In ma trận
print("\nMa trận A:")
for i in range(m):
    for j in range(n):
        print(A[i][j], end = " ")
    print()

# --- a) Tổng tất cả phần tử ---
tong = 0
for i in range(m):
    for j in range(n):
        tong = tong + A[i][j]
print("\na) Tổng các phần tử:", tong)

# --- b) Dòng có tổng lớn nhất ---
tong_max = -1
# Tính tổng từng dòng, tìm tổng lớn nhất
ds_tong_dong = []
for i in range(m):
    t = sum(A[i])
    ds_tong_dong.append(t)
    if t > tong_max:
        tong_max = t

print(f"\nb) Dòng có tổng lớn nhất (tổng = {tong_max}):")
for i in range(m):
    if ds_tong_dong[i] == tong_max:
        print(f"   Hàng {i + 1}:", A[i])

# --- c) Các giá trị phân biệt ---
da_in = []
for i in range(m):
    for j in range(n):
        if A[i][j] not in da_in:
            da_in.append(A[i][j])

print("\nc) Các giá trị phân biệt:", da_in)

# --- d) Tìm số lần xuất hiện và vị trí ---
so = int(input("\nd) Nhập số cần tìm: "))
dem = 0
vi_tri = []
for i in range(m):
    for j in range(n):
        if A[i][j] == so:
            dem += 1
            vi_tri.append((i + 1, j + 1))

if dem == 0:
    print(f"Số {so} không xuất hiện trong ma trận.")
else:
    print(f"Số {so} xuất hiện {dem} lần tại các vị trí (hàng, cột):")
    for vt in vi_tri:
        print(f"   ({vt[0]}, {vt[1]})")

Giải thích từng phần:

Phần nhập và in: Mỗi hàng nhập dưới dạng chuỗi, tách bằng split() rồi chuyển sang số nguyên. In ma trận bằng hai vòng for lồng nhau, end = " " giúp các phần tử cùng hàng in trên một dòng, print() xuống dòng sau mỗi hàng.

Câu a: Duyệt tất cả phần tử bằng hai vòng for, cộng dồn vào biến tong.

Câu b: Tính tổng từng dòng bằng sum(A[i]), lưu vào danh sách ds_tong_dong để so sánh sau. Tìm tong_max rồi duyệt lại – dòng nào có tổng bằng tong_max thì in ra. Cách này xử lí được trường hợp nhiều dòng cùng có tổng lớn nhất.

Câu c: Dùng danh sách da_in làm bộ lọc trùng lặp. Duyệt toàn bộ ma trận, nếu giá trị chưa có trong da_in thì thêm vào.

Câu d: Duyệt toàn bộ ma trận, mỗi khi gặp phần tử bằng số cần tìm thì tăng biến đếm và ghi nhận vị trí (i + 1, j + 1) (cộng 1 vì người dùng quen đánh số từ 1). Dùng danh sách vi_tri chứa các tuple toạ độ để in ra cuối cùng.

Thầy Phạm Thành Danh

Thầy Phạm Thành Danh

(Người kiểm duyệt, ra đề)

Chức vụ: Trưởng ban biên soạn môn Tin Học THPT

Trình độ: Cử nhân Sư phạm Tin học, Chứng chỉ hạng II, Chứng chỉ Tin học, Ngoại ngữ B1

Kinh nghiệm: 8+ năm kinh nghiệm tại Trường THPT Thuận Hóa