Bài 30.4: Bài tập thực hành chuỗi nâng cao trong C++

Bài 30.4: Bài tập thực hành chuỗi nâng cao trong C++
Chào mừng bạn đến với bài thực hành tiếp theo trong chuỗi bài học về C++. Sau khi đã làm quen với những thao tác cơ bản như khai báo, gán giá trị hay nối chuỗi, hôm nay chúng ta sẽ cùng "nhúng tay" vào các bài tập "khó nhằn" hơn một chút với string
- công cụ đắc lực khi làm việc với văn bản trong C++. Chuỗi trong C++ không chỉ đơn thuần là một dãy ký tự, mà nó còn cung cấp rất nhiều phương thức (methods) mạnh mẽ giúp chúng ta thao tác hiệu quả với dữ liệu dạng văn bản.
Bài viết này sẽ tập trung vào việc thực hành các kỹ thuật nâng cao hơn như tìm kiếm, thay thế, trích xuất, chèn, xóa và tách chuỗi. Thông qua các ví dụ minh họa cụ thể, bạn sẽ hiểu rõ hơn cách áp dụng các phương thức của string
vào giải quyết các vấn đề thực tế.
Hãy cùng khám phá sức mạnh tiềm ẩn của string
qua các ví dụ thực tế nhé!
1. Tìm kiếm và Định vị trong Chuỗi
Trong lập trình thực tế, việc tìm kiếm một ký tự hoặc một chuỗi con bên trong một chuỗi lớn là cực kỳ phổ biến. Thư viện chuẩn C++ cung cấp cho chúng ta nhiều phương thức mạnh mẽ để làm điều này với string
.
Tìm kiếm chuỗi con: find
và rfind
Phương thức find
giúp chúng ta tìm vị trí xuất hiện đầu tiên của một chuỗi con (hoặc một ký tự) trong chuỗi hiện tại. Nếu tìm thấy, nó trả về chỉ số (index) của vị trí tìm được. Nếu không tìm thấy, nó trả về một giá trị đặc biệt là string::npos
.
Phương thức rfind
hoạt động tương tự, nhưng nó tìm kiếm vị trí xuất hiện cuối cùng.
Ví dụ:
#include <iostream>
#include <string>
int main() {
using namespace std;
string s = "Hello world, welcome to the C++ world!";
string tu = "world";
string khongTimThay = "programming";
size_t p1 = s.find(tu);
if (p1 != string::npos) {
cout << "Tim thay '" << tu << "' lan dau tai vi tri: " << p1 << endl;
} else {
cout << "'" << tu << "' khong tim thay." << endl;
}
size_t p2 = s.rfind(tu);
if (p2 != string::npos) {
cout << "Tim thay '" << tu << "' lan cuoi tai vi tri: " << p2 << endl;
} else {
cout << "'" << tu << "' khong tim thay." << endl;
}
size_t p3 = s.find(khongTimThay);
if (p3 != string::npos) {
cout << "Tim thay '" << khongTimThay << "' tai vi tri: " << p3 << endl;
} else {
cout << "'" << khongTimThay << "' khong tim thay." << endl;
}
return 0;
}
Output:
Tim thay 'world' lan dau tai vi tri: 6
Tim thay 'world' lan cuoi tai vi tri: 30
'programming' khong tim thay.
Giải thích:
Trong ví dụ trên, chúng ta sử dụng find
để tìm vị trí đầu tiên của "world" và rfind
để tìm vị trí cuối cùng. Kết quả được lưu vào biến kiểu size_t
(kiểu dữ liệu thích hợp cho kích thước và chỉ số). Chúng ta kiểm tra kết quả với string::npos
để biết liệu chuỗi con có được tìm thấy hay không trước khi in ra vị trí.
Tìm kiếm ký tự thuộc/không thuộc một tập hợp: find_first_of
, find_first_not_of
Các phương thức này giúp tìm vị trí của ký tự đầu tiên (hoặc cuối cùng) thỏa mãn một điều kiện nhất định, dựa trên một tập hợp các ký tự cho trước.
find_first_of(set)
: Tìm vị trí ký tự đầu tiên có trong chuỗiset
.find_first_not_of(set)
: Tìm vị trí ký tự đầu tiên không có trong chuỗiset
.- Tương tự có
find_last_of
vàfind_last_not_of
.
Ví dụ:
#include <iostream>
#include <string>
int main() {
using namespace std;
string cau = "C++ Programming is FUN!";
string nguyenAm = "AEIOUaeiou";
size_t pNA = cau.find_first_of(nguyenAm);
if (pNA != string::npos) {
cout << "Nguyen am dau tien o vi tri: " << pNA << " ('" << cau[pNA] << "')" << endl;
}
string chuSo = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
size_t pKC = cau.find_first_not_of(chuSo);
if (pKC != string::npos) {
cout << "Ky tu khong phai chu cai/so dau tien o vi tri: " << pKC << " ('" << cau[pKC] << "')" << endl;
}
return 0;
}
Output:
Nguyen am dau tien o vi tri: 7 ('o')
Ky tu khong phai chu cai/so dau tien o vi tri: 1 ('+')
Giải thích:
Trong ví dụ này, chúng ta tìm vị trí nguyên âm đầu tiên trong chuỗi sentence
bằng cách sử dụng find_first_of
với tập hợp vowels
. Sau đó, chúng ta tìm vị trí ký tự đầu tiên không phải là chữ cái hoặc số bằng find_first_not_of
.
2. Trích xuất và Thay thế Chuỗi con
Thường xuyên chúng ta cần lấy ra một phần của chuỗi hoặc thay đổi một phần của nó. string
cung cấp các phương thức substr
và replace
để thực hiện các thao tác này.
Trích xuất chuỗi con: substr
Phương thức substr(pos, len)
tạo và trả về một chuỗi mới chứa len
ký tự, bắt đầu từ vị trí pos
trong chuỗi gốc. Nếu len
được bỏ qua, nó sẽ trích xuất từ pos
đến hết chuỗi.
Ví dụ:
#include <iostream>
#include <string>
int main() {
using namespace std;
string s = "C++ is a powerful language.";
string p1 = s.substr(0, 3);
cout << "Trich xuat (0, 3): '" << p1 << "'" << endl;
string p2 = s.substr(7);
cout << "Trich xuat (7 den het): '" << p2 << "'" << endl;
return 0;
}
Output:
Trich xuat (0, 3): 'C++'
Trich xuat (7 den het): 'a powerful language.'
Giải thích:
substr(0, 3)
lấy 3 ký tự đầu tiên (từ index 0). substr(7)
lấy từ vị trí thứ 7 (index 7) cho đến cuối chuỗi. Phương thức này không thay đổi chuỗi gốc mà trả về một chuỗi mới.
Thay thế chuỗi con: replace
Phương thức replace(pos, len, str)
thay thế len
ký tự trong chuỗi gốc, bắt đầu từ vị trí pos
, bằng nội dung của chuỗi str
. Có nhiều phiên bản replace
khác nhau (thay thế bằng một phần của chuỗi khác, thay thế bằng N ký tự, v.v.), nhưng phiên bản thay thế một đoạn bằng một chuỗi khác là phổ biến nhất.
Ví dụ:
#include <iostream>
#include <string>
int main() {
using namespace std;
string s = "I like C#. I like Java.";
size_t p1 = s.find("C#");
if (p1 != string::npos) {
s.replace(p1, 2, "C++");
}
size_t p2 = s.find("like", p1 + 1);
if (p2 != string::npos) {
size_t pJ = s.find("Java", p2);
if (pJ != string::npos) {
s.replace(pJ, 4, "Python");
}
}
cout << "Chuoi sau khi thay the: " << s << endl;
return 0;
}
Output:
Chuoi sau khi thay the: I like C++. I like Python.
Giải thích:
Chúng ta tìm vị trí của "C#" và sử dụng replace(pos, 2, "C++")
để thay thế 2 ký tự tại vị trí đó bằng "C++". Tương tự, chúng ta tìm và thay thế "Java" bằng "Python". Lưu ý rằng replace
thay đổi trực tiếp chuỗi gốc và có thể thay đổi kích thước của chuỗi.
3. Chèn và Xóa Ký tự/Chuỗi con
Bạn cũng có thể chèn thêm nội dung vào giữa chuỗi hoặc xóa bớt đi một phần không mong muốn.
Chèn: insert
Phương thức insert(pos, str)
chèn chuỗi str
vào chuỗi gốc tại vị trí pos
. Có nhiều phiên bản khác, ví dụ insert(pos, count, char)
chèn count
ký tự char
vào vị trí pos
.
Ví dụ:
#include <iostream>
#include <string>
int main() {
using namespace std;
string s = "Hello world!";
string them = "beautiful ";
s.insert(6, them);
cout << "Chuoi sau khi chen: " << s << endl;
s.insert(5, 3, 'X');
cout << "Chuoi sau khi chen 'X': " << s << endl;
return 0;
}
Output:
Chuoi sau khi chen: Hello beautiful world!
Chuoi sau khi chen 'X': HelloXXX beautiful world!
Giải thích:
insert(6, to_insert)
chèn chuỗi to_insert
vào vị trí index 6. insert(5, 3, 'X')
chèn 3 ký tự 'X' vào vị trí index 5. Phương thức này thay đổi trực tiếp chuỗi gốc.
Xóa: erase
Phương thức erase(pos, len)
xóa len
ký tự khỏi chuỗi gốc, bắt đầu từ vị trí pos
. Nếu len
được bỏ qua, nó xóa từ pos
đến hết chuỗi. Phiên bản erase(iterator)
xóa ký tự tại vị trí iterator.
Ví dụ:
#include <iostream>
#include <string>
#include <algorithm>
int main() {
using namespace std;
string s1 = "Some extra text here.";
s1.erase(0, 5);
cout << "Sau khi xoa 5 ky tu dau: " << s1 << endl;
s1.erase(6);
cout << "Sau khi xoa tu vi tri 6 den het: " << s1 << endl;
string s2 = "abcdefg";
s2.erase(s2.begin() + 2);
cout << "Sau khi xoa ky tu 'c': " << s2 << endl;
string s3 = " Hello world !";
s3.erase(remove(s3.begin(), s3.end(), ' '), s3.end());
cout << "Sau khi xoa khoang trang: '" << s3 << "'" << endl;
return 0;
}
Output:
Sau khi xoa 5 ky tu dau: extra text here.
Sau khi xoa tu vi tri 6 den het: extra
Sau khi xoa ky tu 'c': abdefg
Sau khi xoa khoang trang: 'Helloworld!'
Giải thích:
erase(0, 5)
xóa 5 ký tự đầu tiên. erase(6)
xóa từ vị trí 6 đến cuối. erase(another.begin() + 2)
sử dụng iterator để xóa ký tự tại vị trí index 2. Ví dụ cuối cùng cho thấy cách kết hợp remove
(từ thư viện <algorithm>
) với erase
để xóa tất cả các lần xuất hiện của một ký tự cụ thể một cách hiệu quả.
4. Tách Chuỗi (String Splitting)
Một bài tập phổ biến khác là chia một chuỗi lớn thành nhiều chuỗi con dựa trên một ký tự phân tách (delimiter), ví dụ như dấu phẩy, dấu chấm phẩy hoặc khoảng trắng. Mặc dù string
không có phương thức split
"có sẵn" như Python hay Java, chúng ta hoàn toàn có thể tự xây dựng hoặc sử dụng các công cụ khác trong C++.
Cách thông dụng và hiệu quả là sử dụng stringstream
, đặc biệt khi phân tách bằng khoảng trắng hoặc các ký tự phân tách đơn giản. Hoặc chúng ta có thể tự viết hàm sử dụng find
và substr
.
Sử dụng stringstream
(Tách theo khoảng trắng mặc định)
Ví dụ:
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
int main() {
using namespace std;
string cau = "C++ programming is powerful and flexible";
stringstream sS(cau);
string tu;
vector<string> dsTu;
while (sS >> tu) {
dsTu.push_back(tu);
}
cout << "Cac tu sau khi tach:" << endl;
for (const auto& t : dsTu) {
cout << "- " << t << endl;
}
return 0;
}
Output:
Cac tu sau khi tach:
- C++
- programming
- is
- powerful
- and
- flexible
Giải thích:
Chúng ta tạo một stringstream
từ chuỗi cần tách. Sử dụng toán tử >>
của stringstream
sẽ tự động đọc các "từ" (các chuỗi được phân tách bởi khoảng trắng) cho đến khi hết luồng. Mỗi từ đọc được sẽ được lưu vào biến word
và sau đó thêm vào vector<string>
. Đây là cách rất tiện lợi để tách chuỗi theo khoảng trắng hoặc các ký tự phân tách "trắng" khác.
Tự xây dựng hàm tách theo ký tự phân tách tùy chỉnh
Nếu bạn muốn tách chuỗi theo một ký tự phân tách cụ thể (ví dụ: dấu phẩy trong file CSV), bạn có thể tự xây dựng một hàm dựa trên find
và substr
.
Ví dụ:
#include <iostream>
#include <string>
#include <vector>
vector<string> tach(const string& chuoi, char kyTuPT) {
vector<string> ds;
string phu;
size_t dau = 0;
size_t cuoi = chuoi.find(kyTuPT);
while (cuoi != string::npos) {
phu = chuoi.substr(dau, cuoi - dau);
ds.push_back(phu);
dau = cuoi + 1;
cuoi = chuoi.find(kyTuPT, dau);
}
phu = chuoi.substr(dau);
ds.push_back(phu);
return ds;
}
int main() {
using namespace std;
string duLieu = "apple,banana,cherry,date";
char phanChia1 = ',';
vector<string> kq1 = tach(duLieu, phanChia1);
cout << "Cac phan sau khi tach boi '" << phanChia1 << "':" << endl;
for (const auto& p : kq1) {
cout << "- " << p << endl;
}
string cau = "This.is.a.sentence.with.dots";
char phanChia2 = '.';
vector<string> kq2 = tach(cau, phanChia2);
cout << "Cac phan sau khi tach boi '" << phanChia2 << "':" << endl;
for (const auto& p : kq2) {
cout << "- " << p << endl;
}
return 0;
}
Output:
Cac phan sau khi tach boi ',':
- apple
- banana
- cherry
- date
Cac phan sau khi tach boi '.':
- This
- is
- a
- sentence
- with
- dots
Giải thích:
Hàm split
này sử dụng vòng lặp và phương thức find
cùng substr
. Nó tìm vị trí của ký tự phân tách, trích xuất chuỗi con trước đó, rồi tiếp tục tìm từ vị trí sau ký tự phân tách. Vòng lặp dừng khi không còn tìm thấy ký tự phân tách nữa, và phần cuối cùng của chuỗi sẽ được thêm vào kết quả. Đây là một kỹ thuật phổ biến khi bạn cần kiểm soát chính xác ký tự phân tách là gì.
5. Bài tập tự luyện
Lý thuyết và ví dụ giúp bạn hiểu cách thức hoạt động, nhưng thực hành mới là yếu tố quyết định để bạn làm chủ được các kỹ năng này. Dưới đây là một vài bài tập để bạn thử sức, kết hợp các kỹ thuật đã học:
- Đếm số lần xuất hiện của từ: Viết chương trình nhập vào một chuỗi (đoạn văn) và một từ cần tìm. Đếm xem từ đó xuất hiện bao nhiêu lần trong chuỗi (bạn có thể tùy chọn có phân biệt chữ hoa/thường hay không). Gợi ý: Sử dụng
find
trong vòng lặp và cập nhật vị trí tìm kiếm sau mỗi lần tìm thấy. - Đảo ngược từng từ trong câu: Nhập vào một câu. Hãy đảo ngược thứ tự các chữ cái trong mỗi từ của câu, nhưng giữ nguyên vị trí của các từ và khoảng trắng. Ví dụ: "Hello world" -> "olleH dlrow". Gợi ý: Tách câu thành các từ, đảo ngược từng từ, rồi ghép lại.
- Kiểm tra Palindrome (nâng cao): Nhập vào một chuỗi. Kiểm tra xem chuỗi đó có phải là palindrome hay không khi bỏ qua khoảng trắng, dấu câu và không phân biệt chữ hoa/thường. Ví dụ: "A man, a plan, a canal: Panama" là palindrome. Gợi ý: Tạo một chuỗi "sạch" chỉ chứa các chữ cái thường, sau đó so sánh nó với chuỗi đảo ngược của chính nó.
- Trích xuất thông tin từ chuỗi định dạng: Giả sử bạn có các chuỗi theo định dạng "Tên: [Tên], Tuổi: [Tuổi], Nghề nghiệp: [Nghề nghiệp]". Viết chương trình để trích xuất Tên, Tuổi, Nghề nghiệp từ các chuỗi này. Gợi ý: Dùng
find
để tìm các nhãn ("Tên:", "Tuổi:", ...) vàsubstr
để lấy phần thông tin sau đó.
Hãy tự mình cố gắng giải các bài tập này trước khi tìm kiếm lời giải nhé! Đó là cách tốt nhất để củng cố kiến thức và kỹ năng của bạn với string
.
Comments