Bài giảng Lập trình nâng cao: Tìm kiếm và đếm cung cấp cho người học các kiến thức: Máy chơi Hangman, chương trình phức tạp, thư viện tập hợp, thư viện ánh xạ, vòng lặp for trên vector,... Mời các bạn cùng tham khảo nội dung chi tiết.
Trang 1Simple AI
9 - Tìm kiếm và đếm
https://github.com/tqlong/advprogram
Trang 2Nội dung
● Máy chơi Hangman
● Chương trình phức tạp → Mã giả + chia để trị
● AI = Dữ liệu + Tìm kiếm + Đếm (thống kê)
● Kỹ thuật:
○ Thư viện tập hợp <set> , thư viện ánh xạ <map>
○ Vòng lặp for trên vector, set, map
Trang 3Đặt vấn đề
Lập trình cho máy chơi trò Hangman:
● Người nghĩ từ
● Máy đoán các chữ cái
● Người trả lời các vị trí chữ cái đoán đúng
…
Người - chủ trò (host); Máy - người chơi (player)
Trang 4đoán của máy và giá treo (đã làm)
đưa ra và secretWord hiện
thời
theo
Hangman giỏi hơn con người ?
Trang 5Nhập trả lời của người chơi
Khi máy đưa ra phán đoán, người chơi trả lời
bằng xâu mặt nạ (mask)
● Một xâu ký tự toàn dấu gạch ngang
● Chỉ hiển thị các vị trí đoán đúng
Ví dụ: người nghĩ từ “ hangm an”
máy đoán p, người trả lời
-máy đoán tiếp a, người trả lời tiếp
-a -a-máy đoán tiếp g, người trả lời tiếp
Trang 6-a-g-a-Tiện ích sinh xâu mặt nạ
// genm ask.cpp
// M ask generating tool for H angm an gam e
# include < iostream >
# include < cctype>
using nam espace std;
int m ain(in t argc, char* argv[])
string w ord = argv[1];
char guess = tolow er(argv[2][0]);
for (un sign ed in t i = 0; i < w ord.length(); i+ + )
if (tolow er(w ord[i]) != guess) w ord[i] = '-';
else w ord[i] = guess;
cout < < w ord < < endl;
return ;
}
Chuyển word
sang mặt nạ, các ký tự khác
guess biến
thành dấu gạch ngang
Trang 7Mã giả - chia để trị
w ordLength = getU serW ordLength();
secretW ord = string(w ordLength, '-');
incorrectG uess = 0;
previousG uesses = em pty set of characters;
stop = false;
do {
guess = getN extG uess(previousG uesses, secretW ord);
m ask = getU serAnsw er(guess);
update(guess, m ask, incorrectG uess, previousG uesses, secretW ord, stop);
render(incorrectG uess, previousG uesses, secretW ord);
} w hile (!stop);
playAnim ation(incorrectG uess = = M AX_G U ESSES, secretW ord);
Trí tuệ nhân tạo (AI)
Trang 8Lập trình nhóm
● Dự án phức tạp nhiều người
● Dự án này
làm giao diện có thể phát triển độc lập
■ Đồng thời, bên làm AI có thể tìm cách cải tiến
Trang 9Tạo Project
● Trong CodeBlocks tạo Project SimpleAI
● Tạo tệp guesser.h, guesser.cpp thêm vào Project
Trang 10Giới thiệu thư viện <set>
cái đã đoán
● Các phần tử trong tập hợp đảm bảo luôn khác nhau ( != )
Trang 11Giới thiệu thư viện <set>
● Các phép toán tập hợp:
Trang 12getNextGuess đơn giản
Chọn ngẫu nhiên 1 ký
tự chưa đoán bao giờ
● Thêm util.* vào
using nam espace std;
char getN extG u ess(const set<char> &
previousG uesses, const string& secretW ord){
set<char> rem ainingChars = getRem ainingChars(previousG uesses);
if (rem ainingChars.size() = = 0) return ;
else
return selectRandom Char(rem ainingChars);
}
guesser.cpp
Trang 13set< ch ar > rem ainingChars;
for ( ch ar c = 'a' ; c < = 'z' ; c+ + ) rem ainingChars.insert(c);
for ( ch ar c: previousG uesses) rem ainingChars.erase(c);
return rem ainingChars;
}
Trang 14Google “c++ select random element from set”
http://stackoverflow.com/questions/3052788/how-to-select-a-random-el ement-in-stdset
ch ar selectR and om C h ar ( con st set< ch ar > & s) {
in t r = rand() % s.size();
for (char c : s) {
if (r = = 0) return c;
} return 0;
}
Trang 15Lập trình giao diện
● Đã có lõi AI đơn giản
● Có thể phát triển giao diện riêng rẽ
● Người làm AI tiếp tục tìm hiểu để cải tiến cách phán đoán (thuật toán)
Trang 16main(): chuyển từ mã giả sang
initialize(w ordLength, secretW ord, incorrectG uess, previousG uesses, stop);
render(incorrectG uess, previousG uesses, secretW ord);
do { char guess = getN extG uess(previousG uesses, secretW ord);
string m ask = getU serAnsw er(guess);
up date(guess, m ask, incorrectG u ess, p reviousG uesses, secretW ord, stop);
render(incorrectG uess, previousG uesses, secretW ord);
} w hile (!stop);
playAnim ation(incorrectG uess = = M AX_G U ESSES, secretW ord);
retu rn ;}
Trang 17Nhập độ dài từ người chơi nghĩ
in t g etU serW ordLen g th () {
Trang 18string answ er;
cout < < endl < < "I guess " < < guess < < ", please enter your m ask: ";
cin > > answ er;
transform (answ er.begin(), answ er.end(), answ er.begin(), ::tolow er);
return answ er;
}
Trang 19Khởi tạo các trạng thái của trò chơi
void in itialize ( in t & w ordLength, string& secretW ord,
in t & incorrectG uess, set< ch ar > & previousG uesses,
b ool & stop)
{
w ordLength = getU serW ordLength();
secretW ord = string(w ordLength, '-' );
incorrectG uess = 0 ;
previousG uesses = set< ch ar > ();
stop = false ;
}
Trang 20Sử dụng lại các hàm trong draw.* (nhớ include)
for (char c: previousGuesses) in các phần tử
void ren der ( in t incorrectG uess, const set< ch ar > &
cout < < " secretW ord = " < < secretW ord < < endl;
cout < < getD raw ing(incorrectG uess) < < endl;
}
Trang 21void p layA n im ation ( b ool isLosing, const string& w ord)
Trang 22update(): viết như kể chuyện
void update ( char guess, const string& m ask,
int & incorrectG uess, set< char > & previousG uesses,
string& secretW ord, bool & stop)
tăng incorrectG uess
nếế u incorrectG uess = = M AX_G U ESSES (7), stop = true
N gược lại
cập nhật secretW ord dựa vào m ặt nạ
nếế u secretW ord không còn dấế u gạch ngang, stop = true
}
Trang 23update(): viết như kể chuyện
void update ( char guess, const string& m ask,
int & incorrectG uess, set< char > & previousG uesses,
string& secretW ord, bool & stop)
{
if (!isG oodM ask(guess, m ask, secretW ord))
throw invalid_argum ent("m istake entering answ er");
updateSecretW ord(m ask, secretW ord);
if (isAllN otD ash(secretW ord)) stop = true ;
}
}
Trang 24isAllDash(): trong util.*
Kiểm tra toàn bộ chữ cái là dấu gạch ngang
b ool isA llD ash ( const string& s) {
for ( unsign ed in t i = 0 ; i < s.length(); i+ + )
if (s[i] != '-' ) return false ; return true ;
}
Trang 25isAllDash(): trong util.*
Kiểm tra toàn bộ chữ cái là dấu gạch ngang
b ool isA llD ash ( const string& s) {
for ( ch ar c : s)
if (c != '-' ) return false ; return true ;
}
Trang 26isAllNotDash(): trong util.*
Kiểm tra toàn bộ chữ cái không là dấu gạch ngang
b ool isA llN otD ash ( con st string& s) {
for ( unsign ed in t i = 0 ; i < s.length(); i+ + )
if (s[i] = = '-' ) return false ; return true ;
}
Trang 27isAllNotDash(): trong util.*
Kiểm tra toàn bộ chữ cái không là dấu gạch ngang
b ool isA llN otD ash ( con st string& s) {
for ( ch ar c : s)
if (c = = '-' ) return false ; return true ;
}
Trang 28Hiển thị các chữ cái trong mặt nạ (mask)
void up d ateS ecretW ord ( con st string& m ask, string& secretW ord) {
for ( un sig n ed in t i = 0 ; i < secretW ord.length(); i+ + )
if (m ask[i] != '-' )
secretW ord[i] = m ask[i];
}
Trang 29b ool isG oodM ask ( char guess, const string& m ask,
const string& secretW ord)
{
if (m ask.length() != secretW ord.length()) return false ;
for ( unsigned int i = 0 ; i < secretW ord.length(); i+ + )
độ dài phải bằng nhau
nếu mask[i] là chữ cái thì mask[i] phải bằng
guess và secretWord[i]
phải là dấu gạch hoặc
phải bằng mask[i]
Trang 30Sửa hàm main() bắt ngoại lệ
Đến đây, mỗi người có thể làm phần của mình độc lập
string m ask = getU serAnsw er(guess);
update(guess, m ask, incorrectG uess, previousG uesses, secretW ord, stop);
break;
} catch (invalid_argum ent e) {
cout < < "Invalid m ask, try again" < < endl;
}
} w hile (true);
render(incorrectG uess, previousG uesses, secretW ord);
} w hile (!stop);
Trang 31Nội dung
● Máy chơi Hangman
● Chương trình phức tạp → Mã giả + chia để trị
● AI = Dữ liệu + Tìm kiếm + Đếm (thống kê)
● Kỹ thuật:
○ Thư viện tập hợp <set> , ánh xạ <map>
○ Vòng lặp for trên vector, set, map
Trang 32Simple AI
● may rủi, có tỉ lệ thua cao
● đơn giản, dễ cài đặt
→ dùng làm thuật toán tạm thời để phát triển
độc lập các thành phần của chương trình
Trang 33Simple AI
Cải tiến getNextGuess() như con người chơi
● B0: Chuẩn bị vốn từ vựng tiếng Anh
● B1: Thử đoán các nguyên âm a, e, i, o, u
● B2: Sau khi đoán đúng một số vị trí
Trang 34B0: Chuẩn bị vốn từ vựng tiếng Anh
char getN extG uess(const set<char> & previousG uesses,
const string& secretW ord){
static vector< string> w ordList = readW ordListFrom File("data/O gden_Picturable_200.txt");
set<char> rem ainingChars = getRem ainingChars(previousG uesses);
// TO D O : m ake a guess (B 1, B 2)
}
guesser.cpp
Trang 35B1: ban đầu đoán nguyên âm
âm chưa đoán trong a, e, i, o, u để đoán
set< ch ar > rem ainingChars = getRem ainingChars(previousG uesses);
if (rem ainingChars.size() = = 0 )
return ;
if (isAllD ash(secretW ord))
return getVow elG uess(rem ainingChars);
Trang 36
getVowelGuess(): tìm nguyên âm
Trả về 0 nếu không tìm thấy nguyên âm
char getVow elG uess(const set<ch ar> & rem ainingChars)
{
char vow el[] = {'a', 'e', 'i', 'o', 'u'};
for (int i = 0; i < 5; i+ + ) {
if (rem ainingChars.find(vow el[i]) != rem ainingChars.end())
retu rn vow el[i];
Trang 37getVowelGuess(): tìm nguyên âm
Vòng lặp for trên vector
char getVow elG uess(const set<ch ar> & rem ainingChars)
{
char vow el[] = {'a', 'e', 'i', 'o', 'u'};
for (char c : vow el) {
if (rem ainingChars.find(c) != rem ainingChars.end())
Trang 38getVowelGuess(): thứ tự tìm
Đoán nguyên âm có tần suất xuất hiện cao trước
● Google: letter frequency
○ Thứ tự: e, a, o, i, u
● Cũng có thể tính tần suất các ký tự trong từ vựng của mình (sẽ làm)
ch ar vow el[] = {'e', 'a', 'o', 'i', 'u'};
Trang 39B2: lọc từ và chọn chữ cái
● Lọc từ trong từ vựng
○ Có các chữ cái ở vị trí giống secretWord
(trừ dấu gạch ngang)
vector< string> fi lteredW ordList =
getSuitableW ords(w ordList, secretW ord, rem ainingChars);
Trang 40Duyệt mảng wordList để tìm các từ phù hợp
vector< string> getSuitableW ords( const vector< string> & w ordList,
const string& secretW ord,
const set< ch ar > & rem ainingChars)
{
vector< string> result;
for ( un sign ed in t i = 0 ; i < w ordList.size(); i+ + )
if (isSuitableW ord(w ordList[i], secretW ord, rem ainingChars))
result.push_back(w ordList[i]);
return result;
}
thỏa mãn điều kiện thì đưa vào kết quả
Trang 41vector< string> result;
for ( con st string& w ord : w ordList)
if (isSuitableW ord(w ord, secretW ord, rem ainingChars))
result.push_back(w ord);
return result;
}
thỏa mãn điều kiện thì đưa vào kết quả
Trang 42● Có độ dài bằng secretWord
● Các chữ cái ở secretWord hiện đúng vị trí trong word
● Các chữ cái còn lại nằm trong remainingChars
bool isS uitableW ord ( const string& w ord, const string& secretW ord,
const set< char > & rem ainingChars)
{
if (w ord.length() != secretW ord.length()) return false ;
for ( unsigned int i = 0 ; i < w ord.length(); i+ + ) {
Trang 43Bài tập: tách các bộ lọc riêng
vector< string> getSuitableW ords( const vector< string> & w ordList,
con st string& secretW ord,
con st set< ch ar > & rem ainingChars)
{
vector< string> result;
result = fi lterW ordListByLen(secretW ord.length(), w ordList);
result = fi lterW ordListByM ask(secretW ord, result);
result = fi lterW ordListByRem ainingChars(
rem ainingChars, secretW ord, result);
return result;
}
làm các hàm này
Trang 44B2: lọc từ và chọn chữ cái
● Chọn chữ cái
● Chức năng auto-complete của Google
occurenceCount = getO ccurenceCount(rem ainingChars, fi lteredW ordList);
return g etM axO ccuren ceC h ar (rem ainingChars, occurenceCount);
Trang 45Thư viện <map>
● Mỗi chữ cái cần lưu số lần xuất hiện
○ Ánh xạ từ ký tự (char) ra số nguyên (int)
○ Trong C++: map<char, int>
○ Thư viện <map>:
http://www.cplusplus.com/reference/map/map/
Trang 46Thư viện <map>
● Các thao tác với map:
■ 'a' gọi là key , 1 là value
Trang 47Duyệt các phần tử của map
● Mỗi phần tử của map có dạng
● Duyệt qua map
for (auto p: my_map)
cout << p.first << p.second << endl;
Trang 48● Khởi tạo count là map<char, int>
remainingChars
● Duyệt qua các từ, với mỗi từ
○ Duyệt qua từng chữ cái
○ Tăng số đếm tương ứng trong count thêm 1
Trang 49Tăng số đếm các ký tự trong danh sách từ
m ap<char, int> getO ccurenceCount(const set<char> & rem ainingChars,
const vector< string> & w ordList)
{
m ap<char, int> count;
for (char c: rem ainingChars) count[c] = 0;
for (unsig ned int i = 0; i < w ordList.size(); i+ + ) {
const string& w ord = w ordList[i];
for (unsigned int j = 0; j < w ord.length(); j+ + )
if (count.find(w ord[j]) != count.end())
count[w ord[j]]+ + ;
}
retu rn count;
}
Trang 50Chuyển hết qua lệnh for mới
m ap<char, int> getO ccurenceCount(const set<char> & rem ainingChars, const vector< string> & w ordList)
{
m ap<char, int> count;
for (char c: rem ainingChars) count[c] = 0;
for (con st string& w ord : w ordList) {
for (char c : w ord)
Trang 51Duyệt các cặp (key, value) trong count
Nếu value > best_count thì gán best bằng c và
char getM axO ccurenceC har(const set<char> & rem ainingChars, const m ap<char, in t> & count)
{ char best = 0; int best_count = 0; for (au to p : count)
if (p.second > best_count) { best = p.first;
best_count = p.second;
} return best;
}
Trang 52Simple AI 1.0
char g etN extG u ess(const set<char> & previousG uesses, const string& secretW ord)
{
static vector< string> w ordList = readW ordListFrom File("data/O gden_Picturable_200.txt");
set<char> rem ainingChars = getRem ainingChars(previousG uesses);
if (rem ainingChars.size() = = 0)
return ;
if (isAllD ash(secretW ord))
return getVow elG uess(rem ainingChars);
vector< string> filteredW ordList = getSuitableW ords(w ordList, secretW ord, rem ainingChars);
m ap<ch ar, int> occurenceCount = getO ccurenceCount(rem ainingChars, filteredW ordList);
retu rn getM axO ccurenceChar(rem ainingChars, occurenceCount);
} // chỉỉ có hàm này được khai báo ởỉ guesser.h, các hàm khác chỉỉ nằm trong guesser.cp p
https://github.com/tqlong/advprogram/archive/9bc66 14903304407ddee771d30cad02cf5051ecb.zip