Xử lý lỗi chính tả tên người chúng ta cần xử lý các trường hợp đặc thù trước.
Lỗi chính tả tên người rất phong phú và đa dạng, bắt lỗi có thể không quá khó, nhưng sửa như thế nào lại là vấn đề, vì tùy tình huống bối cảnh khác nhau mà cách xử lý cũng không thể giống nhau được.
Trong lỗi chính tả có những trường hợp đặc thù mà chúng ta cần xử lý trước, vì các trường hợp như vậy việc nhận biết và hướng giải quyết cũng đơn giản hơn, chẳng hạn như:
- Lỗi dính tên;
- Lỗi đặt dấu thanh;
- Lỗi có các ký tự lạ trong họ tên;
- Lỗi có dấu (A), (B), 1, 2 cuối tên;
- Các biến thể sai chính tả do gõ nhầm cũng có thể xây dựng hàm để bắt lỗi khá tốt;
Trong bài viết ngày hôm nay tôi sẽ nói về việc chế hàm tạo tự động các biến thể sai chính tả của một từ nào đó.
Các biến thể sai chính tả do gõ nhầm có thể bao gồm các trường hợp sau:
- Thiếu một ký tự trong họ tên, ví dụ thay vì
nguyễn
thì làngyễn
; - Thừa một ký tự trong họ tên, ví dụ
nguyễnn
- Ký tự bị đảo vị trí, ví dụ như
ngyuễn
- Thiếu dấu thanh trong các từ có dấu thanh, ví dụ
nguyên
- Thiếu hoàn toàn dấu thanh và dấu mũ, ví dụ
nguyen
- Đặt sai vị trí dấu thanh, ví dụ
ngũyen
- Vị trí dấu thanh đúng, nhưng dấu thanh lại sai, ví dụ
nguyển
, cái này rất hay xảy ra với dấu hỏi, ngã và huyền, vì nó nhìn khá giống nhau, ít khi xảy ra với dấu sắc và dấu nặng; - Một ký tự lạ kèm với họ tên, ví dụ như
Tuấnz
Việc xác định lỗi chính tả với họ thì dễ hơn so với tên hoặc đệm, vì họ có trường dữ liệu ổn định, còn tên, đệm thì rất phong phú, về lý thuyết có thể là bất cứ từ nào, thậm chí còn đa dạng hơn văn bản thông thường, vì tên người không có chuẩn chính tả thống nhất được như văn bản- tên người tuân thủ theo quy tắc văn hóa nhiều hơn.
Tên người cũng là dữ liệu rất ngắn gọn, thường chỉ 2 đến 4 từ với khoảng từ 5 đến 20 ký tự, dữ liệu nếu so với văn bản là rất nhỏ. Ví dụ một bài báo có thể có hàng ngàn từ để chúng ta phân tích.
Tuy nhiên họ tên cũng có rất nhiều luật ngầm ẩn mà chúng ta có thể căn cứ vào đó để xử lý hiệu quả hơn, chẳng hạn:
- Chúng ta có số lượng họ nhất định, và gần như cố định;
- Tên người dù có thể là bất cứ từ nào nhưng nó cũng rất tập trung, 500 tên phổ biến có thể chiếm đến hơn 95% số tên thực tế được đặt;
- Tên thường có khuynh hướng nhắm đến ý nghĩa tốt đẹp, nên ý nghĩa xấu có khả năng cao loại trừ, đặc biệt là tên người trẻ;
Tôi sẽ nói về thuật toán xử lý họ sai chính tả trước.
- Kiểm tra họ của một người có trong danh sách họ thực tế không. Nếu không họ đó có thể là viết sai chính tả;
- Kiểm tra xem họ sai chính tả đó gần với các họ đúng nào? Ví dụ
nguye
có thể xem là gần với cả họnguyễn
lẫnngụy
, nhưng khả năng cao là nguyễn nhiều hơn vì có âme
ở cuối. Tuy nhiên nếu văn bản đó làngụye
thì có thể chúng ta không chắc được đây là họ nào; - Nếu mức độ tương tự của một họ sai chính tả với từ 2 họ đúng chính tả rất tương đồng thì sẽ rất khó để chúng ta xác định được đâu là họ đúng; Ví dụ
Pham
có thể làPhan
mà cũng có thể làPhạm
, trên bàn phím, ký tựm
vàn
sát nhau, không chỉ có thế kiểu hình của nó cũng khá tương đồng; - Nếu mức độ chênh lệch là vài lần, trường hợp này sẽ dễ quyết định hơn, mức chênh càng lớn càng dễ chắc chắn;
- Cần phân biệt sai chính tả với viết tắt, một trong những từ viết tắt họ tên người phổ biến nhất là
ng
chỉ cho họnguyễn
. Trong văn bản tên người, ký hiệu viết tắt này hay xuất hiện ở các tên dài 4 từ đổ lên;
Vậy điểm mấu chốt ở đây là chúng ta phải xác định được thuật toán đánh giá mức độ tương đồng giữa họ sai chính tả và họ tồn tại thực tế có khả năng là họ chính xác.
PHP có một số hàm để xác định mức độ tương đồng của 2 chuỗi, khả quan nhất là similar_text
và levenshtein
(những hàm còn lại liên quan đến ngữ âm tiếng Anh, chứ không phải thuần túy văn bản nên không phù hợp với tiếng Việt).
Thuật toán của levenshtein
dễ hiểu hơn, nó đo số thao tác thêm, xóa và thay thế để biến chuỗi thứ nhất thành chuỗi thứ hai, giá trị này càng nhỏ thì càng tương đồng. Ví dụ giữa nguyễ
và nguyễn
là 1, vì chỉ cần thêm chữ n
nữa (một thao tác) là hai chuỗi hoàn toàn giống nhau.
similar_text
thì tính theo %, mức độ tương đồng của ví dụ trên có giá trị hơn 93%.
echo levenshtein("nguyễ","nguyễn"); // kết quả là 1
echo "</br>";
similar_text("nguyễ", "nguyễn",$p);
echo $p; // kết quả là 93,3%
Còn khi so với ngụy thì sao:
echo levenshtein("nguyễ","nguỵ"); // kết quả là 2
echo "</br>";
similar_text("nguyễ", "nguỵ",$p);
echo $p; // kết quả là 76,9%
1 thì tốt hơn 2 (với levenshtein
giá trị càng nhỏ càng tương đồng), còn với similar_text
, càng lớn càng tốt, ở đây 93,3% tốt hơn. Như vậy kết quả trường hợp này rất rõ ràng.
Ví dụ khác:
echo levenshtein("nguyen","nguyễn"); // kết quả là 3
echo "</br>";
similar_text("nguyen", "nguyễn",$p);
echo $p; // kết quả là 71,5%
echo levenshtein("nguyen","nguỵ"); // kết quả là 3
echo "</br>";
similar_text("nguyen", "nguỵ",$p);
echo $p; // kết quả là 50%
Trong trường hợp này kết luận của similar_text
gần với thực tế hơn, vì nguyen
gần với nguyễn
hơn nhiều so với nguyen
gần với ngụy
. Kết quả của levenshtein
đánh giá chúng ngang nhau.
Cũng có trường hợp levenshtein
dự đoán chính xác hơn. Chuỗi càng ngắn khả năng levenshtein
sẽ dự đoán càng sai, vì chuỗi ngắn, mức độ lệch chỉ 1 ký tự sẽ ra nghĩa khác nhiều hơn so với chuỗi dài.
Tuy nhiên cả 2 hàm được thiết kế không tính đến chuyện đánh giá ngữ nghĩa, các ký tự phím gần nhau, và tất nhiên không tính đến cả việc kiểu gõ tiếng Việt tạo ra đặc thù riêng trong sai chính tả nhập liệu.
Với giả định như vậy, chúng ta sẽ viết một hàm ngược, tức là đưa các yếu tốt bàn phím vào và kiểu gõ vào rồi đối sánh dữ liệu thực tế xem có đúng là các lỗi đấy xuất hiện thường xuyên hơn không.
Chẳng hạn xuyww
(phím w gần với e) nếu tính đến bàn phím và kiểu gõ telex nó sẽ gần với xuyên
nhiều so với xuy
đáng kể.
Nhưng với 2 hàm trên, kết quả sẽ ngược lại:
echo levenshtein("xuyww","xuy"); // kết quả là 2
echo "</br>";
similar_text("xuyww", "xuy",$p);
echo $p; // kết quả là 75
echo levenshtein("xuyww","xuyên"); // kết quả là 3
echo "</br>";
similar_text("xuyww", "xuyên",$p);
echo $p; // kết quả là 54,5%
Nhưng tất cả hẵn là dự đoán, vì mẫu sai chính tả mà tôi hiện có còn ít, giờ chúng ta sẽ tìm nó bằng cách thử mọi trường hợp có thể xem thế nào, rồi may ra mới có kết luận chắc chắn hơn được.
Các đoạn mã
A. Bớt một ký tự:
$str = "nguyễn"; // mẫu đầu vào ví dụ là nguyễn
$tt = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY); // cắt chuỗi
$ct = count($tt); // số phần tử trong chuỗi
$bot_kt = array(); // mảng chứa chuỗi bớt một ký tự từ chuỗi chuẩn $str
for ($i=0; $i<$ct; $i++) {
$kp = $tt[$i];
$tt[$i] = ''; // bỏ nó
$tg = ''; // gọi là trung gian vì sau khi thực hiện xong nhiệm vụ, nó không cần quan tâm đến nữa
for ($j=0; $j<$ct; $j++) {$tg.=$tt[$j];}
$bot_kt[$i]=$tg; // đưa kết quả có được vào mảng bot
$tt[$i]=$kp;//khôi phục lại ký tự thứ i để vòng lặp sau nó không bị mất giá trị (vòng lặp sau chúng ta xóa ký tự thứ i + 1)
}
Kết quả nó tự động tạo các chuỗi đầu ra sau:
guyễn
nuyễn
ngyễn
nguễn
nguyn
nguyễ
B. Thêm một ký tự đôi (ví nnguyễn
, ngguyễn
):
$them_kt = array(); // thêm một ký tự trong chuỗi
for ($i=0; $i<$ct; $i++) {
$kp = $tt[$i];
$tt[$i] = $kp.$kp;
$tg = '';
for ($j=0; $j<$ct; $j++) {$tg.=$tt[$j];}
$them_kt[$i] = $tg;
$tt[$i] = $kp;//khôi phục lại
}
Kết quả của giải pháp trên:
nnguyễn
ngguyễn
nguuyễn
nguyyễn
nguyễễn
nguyễnn
C. Đảo vị trí 2 ký tự liền nhau
$dao_kt=array(); // đảo vị trí 2 ký tự ở cạnh nhau
$cg = $ct-1; // phải trừ 1 vì vị trí cuối cùng thì không phải đảo nữa
for ($i=0; $i<$cg; $i++) {
$ai = $tt[$i];
$k = $i+1; // ký tự liền sau nên là i + 1
$ak = $tt[$k];
// đảo giá trị cho nhau
$tt[$i]=$ak;
$tt[$k]=$ai;
$tg='';
for ($j=0;$j<$ct;$j++) {$tg.=$tt[$j];}
$dao_kt[$i]=$tg;
$tt[$i]=$ai;//khôi phục lại
$tt[$k]=$ak;// để vòng lặp sau không bị ảnh hưởng
}
Kết quả:
gnuyễn
nugyễn
ngyuễn
nguễyn
nguynễ
3 phần đầu liên quan đến trật tự từ đơn giản hơn tôi nghĩ, tưởng cứ phải viết gì phức tạp quá cơ, hóa ra chỉ cần dùng vòng lặp for
là đã giải quyết được. Giờ chúng ta viết các lỗi liên quan đến ký tự.
D. Xóa dấu của ký tự
Tôi viết hàm xóa dấu riêng, vì cái này còn dùng vào việc khác.
function xoa_dau($str){ // xóa dấu của một ký tự hoặc chuỗi
$dx = array("cc80","cc81","cc83","cc89","cca3"); // mã hóa dấu tiếng Việt
$hz = bin2hex(mahoa_itdung($str)); // chuyển sang mã hex để tìm dấu
$i=0;
foreach ($dx as $dy) {
$dz ='/'.$dy.'/';
if (preg_match($dz, $hz)) {
$hz = preg_replace($dz,'',$hz); // khử dấu của $hz; nó vẫn đang ở dạng hex
$i++;
}
}
$kq = chuyen_ma_hoa(hex2bin($hz));
return $kq;
}
// không dấu
$khongdau = xoa_dau($str);
// Kết quả: nguyên
Trước khi xóa dấu để đỡ mất công, chúng ta nên kiểm tra trước là họ hay tên có dấu không đã bằng hàm sau:
function sl_co_dau($str) { // tìm số lượng từ có dấu trong chuỗi, chỉ nên áp dụng cho một từ
$cd = mang_codau();
$sl_cd = 0;
foreach ($cd as $cd2) {
$cd3='/'.$cd2.'/';
if (preg_match($cd3, $str)) {$sl_cd++;}
}
return $sl_cd;
}
Bạn lưu ý là hàm này chỉ xóa dấu, chứ không xóa mũ như là ê thành e, hay ơ thành o, ngay sau đây chúng ta mới viết hàm như vậy.
E. Bỏ cả dấu và mũ
// bỏ mũ
$bmm = preg_split('//u', $khongdau, -1, PREG_SPLIT_NO_EMPTY); // cắt chuỗi
$abomu = array(); $j=0; $bomu="";
foreach ($bmm as $bmm2) {
if ($bmm2 == "ă" || $bmm2 == "â") {$bmm2="a";}
if ($bmm2 == "ô" || $bmm2 == "ơ") {$bmm2="o";}
if ($bmm2 == "ê") {$bmm2="e";}
if ($bmm2 == "ư") {$bmm2="u";}
$abomu[$j] = $bmm2; $j++;
}
foreach ($abomu as $bbomu) {
$bomu.=$bbomu;
}
// Kết quả: nguyen
F. Đổi sang dấu thanh khác, nhưng giữ nguyên vị trí dấu
// thêm các dấu vào từ
$dau = tim_dau($str);
$kt = catn_kt($dau, 1);
$vt = (int)(1 + catn_kt($dau, -1));
$dod = array(); $j = 0;
if ($vt>1) {
$m = them_dau($kt, 6); // thêm tất cả các biến thể của dấu vào ký tự
foreach ($m as $tt) {
$tg = thay_the($str, $tt, $vt);
if ($tg!=$str) {$dod[$j] = $tg; $j++;}
}
}
Mấy hàm tim_dau
, catn_kt
,.. là tôi viết riêng, bạn có thể tham khảo sửa đổi cũng như cập nhật của nó ở bài này.
Kết quả:
nguyền
nguyến
nguyển
nguyện
nguyễn // cái này trùng với tên gốc, chúng ta sẽ loại bỏ nó sau
G. Thêm dấu cho các nguyên âm khác trong từ đã bỏ hoàn toàn dấu và mũ
Ví dụ nguỹen
hoặc ngũyen
.
Ở đây 5 dấu được bổ sung vào các nguyên âm đã bỏ hết dấu và mũ (tức đầu vào là chuỗi $bomu, ví dụ nguyen
).
$dau = di_tim_dau($str);
$kt = catn_kt($dau, 1);
$vtx = catn_kt($dau, -1);
$vt = (int)($vtx);
$dod = array(); $j = 0;
if ($vt>0) {
$m = them_dau($kt, 6); // thêm tất cả các biến thể có dấu vào ký tự
foreach ($m as $tx) {
$tg = thay_the($str, $tx, $vt);
$dod[$j] = $tg; $j++;
}
$j++; // bổ sung thêm từ không dấu
$dod[$j] = $khongdau;
$j++; // bổ sung thêm từ không dấu, không mũ
$dod[$j] = $bomu;
}
// thêm dấu vào các nguyên âm trong từ gốc hiện không có dấu
// lấy mảng các ký tự không dấu a, ô, ư, vân vân ra để đối chiếu
$mkd = mang_khongdau(); $i=0;
$mthemdau=array(); $j=0;// tạo mảng để chứa các ký tự bổ sung dấu
$tt2 = preg_split('//u', $bomu, -1, PREG_SPLIT_NO_EMPTY); // cắt chuỗi
foreach ($mkd as $mkd2) { // bắn ra các nguyên âm không dấu
foreach ($tt2 as $tt3) { // bắn ra các ký tự của chuỗi
if ($tt3 == $mkd2) { // so sánh ký tự của chuỗi xem nó có các nguyên âm nào, nếu có thì gắn thêm dấu vào
$vt = vi_tri($bomu, $tt3);
$td = them_dau($tt3, 6); // thêm 5 dấu cho nguyên âm
foreach ($td as $tk) { // bắn ra các ký tự đã thêm dấu
$tg2 = thay_the($bomu, $tk, $vt[0]); // thay thế ký tự đó vào chuỗi gốc
$mthemdau[$j] = $tg2; $j++; // thêm ký tự đó vào mảng
}
}
}
}
Kết quả:
nguyèn
nguyén
nguyẽn
nguyẻn
nguyẹn
ngùyen
ngúyen
ngũyen
ngủyen
ngụyen
nguỳen
nguýen
nguỹen
nguỷen
nguỵen
G. Thêm dấu cho các nguyên âm khác trong từ bỏ dấu nhưng vẫn còn mũ
Ví dụ ngũyên
hoặc nguỹên
.
Về bản chất mã đoạn này hệt như ở phần G, chỉ khác là đầu vào là chuỗi vẫn còn mũ. Bạn để đầu vào là chuỗi không dấu của chuỗi gốc là OK. Kết quả:
nguyền
nguyến
nguyễn
nguyển
nguyện
ngùyên
ngúyên
ngũyên
ngủyên
ngụyên
nguỳên
nguýên
nguỹên
nguỷên
nguỵên
Tiếp đến chúng ta tiến hành gộp các mảng lại với nhau và tiến hành loại bỏ các giá trị trùng lặp:
$kq = array_merge($bot_kt,$them_kt,$dao_kt,$dod,$mthemdau); // gộp các mảng vảo với nhau
$ukq = array_unique($kq); // loại bỏ các giá trị trùng lặp
Kết quả tổng hợp các đoạn mã trên giúp xuất ra được dữ liệu sai chính tả như thế này:
guyễn
nuyễn
ngyễn
nguễn
nguyn
nguyễ
nnguyễn
ngguyễn
nguuyễn
nguyyễn
nguyễễn
nguyễnn
gnuyễn
nugyễn
ngyuễn
nguễyn
nguynễ
nguyền
nguyến
nguyển
nguyện
nguyên
nguyen
nguyèn
nguyén
nguyẽn
nguyẻn
nguyẹn
ngùyen
ngúyen
ngũyen
ngủyen
ngụyen
nguỳen
nguýen
nguỹen
nguỷen
nguỵen
ngùyên
ngúyên
ngũyên
ngủyên
ngụyên
nguỳên
nguýên
nguỹên
nguỷên
nguỵên
Ở đây chúng ta thấy có nhiều từ có khả năng cao sẽ là từ sai chính tả trong thực tế (ví dụ nguyển
), nhưng cũng có từ có khả năng rất thấp (ví dụ ngủyên
hoặc nguyễễn
). Nhưng không sao, chúng ta chỉ đang cố tạo ra càng nhiều trường hợp càng tốt để đối sánh. Và sẽ điều chỉnh sau nếu thấy cách thức nào đó không phù hợp.
Đoạn mã trên có thể bổ sung thêm bằng cách chuyển các từ có dấu hoặc/và mũ về kiểu gõ telex sai chính tả, ví dụ nguyeenx
hoặc nguyeen
. Công đoạn cuối là ta sẽ phải lọc danh sách trên để loại các từ trùng với danh sách họ hiện có.
Mã hoàn chỉnh:
function saichinhta_ho($str) {
$tt = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY); // cắt chuỗi
$ct = count($tt); // số phần tử trong chuỗi
// bỏ một ký tự trong từ
$bot_kt=array(); // mảng chứa chuỗi bớt một ký tự từ chuỗi chuẩn $str
if ($ct>3) {
// yêu cầu số lượng ký tự tối thiểu là 3 thì mới thực hiện việc chuyển đổi này
// nếu từ 3 trở xuống mảng trả về là rỗng
for ($i=0; $i<$ct; $i++) {
$kp = $tt[$i];
$tt[$i] = ''; // bỏ nó
$tg = ''; // gọi là trung gian vì sau khi thực hiện xong nhiệm vụ, nó không cần quan tâm đến nữa
for ($j=0; $j<$ct; $j++) {$tg.=$tt[$j];}
$bot_kt[$i]=$tg; // đưa kết quả có được vào mảng bot
$tt[$i]=$kp;//khôi phục lại để vòng lặp sau nó không bị mất giá trị
}
}
///////////////////////////////////////////////////////////////////////////////////
// nhân đôi một ký tự
$them_kt = array(); // thêm một ký tự trong chuỗi
for ($i=0; $i<$ct; $i++) {
$kp = $tt[$i];
$tt[$i] = $kp.$kp;
$tg = '';
for ($j=0; $j<$ct; $j++) {$tg.=$tt[$j];}
$them_kt[$i] = $tg;
$tt[$i] = $kp;//khôi phục lại
}
////////////////////////////////////////////////////////////////////////////////////
// đảo ký tự
$dao_kt=array(); // đảo vị trí 2 ký tự ở cạnh nhau
$cg = $ct-1; // phải trừ 1 vì vị trí cuối cùng thì không phải đảo nữa
for ($i=0; $i<$cg; $i++) {
$ai = $tt[$i];
$k = $i+1; // ký tự liền sau nên là i + 1
$ak = $tt[$k];
// đảo giá trị cho nhau
$tt[$i]=$ak;
$tt[$k]=$ai;
$tg='';
for ($j=0;$j<$ct;$j++) {$tg.=$tt[$j];}
$dao_kt[$i]=$tg;
$tt[$i]=$ai;//khôi phục lại
$tt[$k]=$ak;// để vòng lặp sau không bị ảnh hưởng
}
////////////////////////////////////////////////////////////////////////////
// Không dấu
$khong_dau = xoa_dau($str);
//////////////////////////////////////////////////////////////////////////////
$bomu = $khong_dau;
$bmm = preg_split('//u', $bomu, -1, PREG_SPLIT_NO_EMPTY); // cắt chuỗi
$abomu = array(); $j=0; $bomu="";
foreach ($bmm as $bmm2) {
if ($bmm2 == "ă" || $bmm2 == "â") {$bmm2="a";}
if ($bmm2 == "ô" || $bmm2 == "ơ") {$bmm2="o";}
if ($bmm2 == "ê") {$bmm2="e";}
if ($bmm2 == "ư") {$bmm2="u";}
$abomu[$j] = $bmm2; $j++;
}
if (count($abomu)) {
foreach ($abomu as $bbomu) {
$bomu.=$bbomu;
}}
///////////////////////////////////////////////////////////////////////////////////////
// thêm các dấu vào từ ban đầu đã có dấu
$themdau2 = array(); $j = 0;
$dau = di_tim_dau($str);
$kt = catn_kt($dau, 1);
$vtx = catn_kt($dau, -1);
$vt = (int)($vtx); // mới ra vị trí thực trong mảng
if ($vt>0) {
$m = them_dau($kt, 6); // thêm tất cả các biến thể có dấu vào ký tự
foreach ($m as $tx) {
$tg = thay_the($str, $tx, $vt);
$themdau2[$j] = $tg; $j++;
}
$j++; // bổ sung thêm từ không dấu
$themdau2[$j] = $khong_dau;
$j++; // bổ sung thêm từ không dấu, không mũ
$themdau2[$j] = $bomu;
}
//////////////////////////////////////////////////////////////
// thêm dấu vào các nguyên âm trong từ gốc hiện không có dấu
// lấy mảng các ký tự không dấu a, ô, ư, vân vân ra để đối chiếu
$mkd = mang_khongdau(); $i=0;
$themdau3=array(); $j=0;// tạo mảng để chứa các ký tự bổ sung dấu
$tt2 = preg_split('//u', $bomu, -1, PREG_SPLIT_NO_EMPTY); // cắt chuỗi
foreach ($mkd as $mkd2) { // bắn ra các nguyên âm không dấu
foreach ($tt2 as $tt3) { // bắn ra các ký tự của chuỗi
if ($tt3 == $mkd2) { // so sánh ký tự của chuỗi xem nó có các nguyên âm nào, nếu có thì gắn thêm dấu vào
$vt = vi_tri($bomu, $tt3);
$td = them_dau($tt3, 6); // thêm 5 dấu cho nguyên âm
foreach ($td as $tk) { // bắn ra các ký tự đã thêm dấu
$tg2 = thay_the($bomu, $tk, $vt[0]); // thay thế ký tự đó vào chuỗi gốc
$themdau3[$j] = $tg2; $j++; // thêm ký tự đó vào mảng
}
}
}
}
///////////////////////////////////////////////////////////////////////////
// thêm dấu cho nguyên âm khác trong từ vẫn còn mũ
$khongdau2 = xoa_dau($str);
$themdau4=array(); $i=0; $j=0;// tạo mảng để chứa các ký tự bổ sung dấu
$tt2x = preg_split('//u', $khongdau2, -1, PREG_SPLIT_NO_EMPTY); // cắt chuỗi
foreach ($mkd as $mkd2x) { // bắn ra các nguyên âm không dấu
foreach ($tt2x as $tt3x) { // bắn ra các ký tự của chuỗi
if ($tt3x == $mkd2x) { // so sánh ký tự của chuỗi xem nó có các nguyên âm nào, nếu có thì gắn thêm dấu vào
$vtx = vi_tri($khongdau2, $tt3x);
$tdx = them_dau($tt3x, 6); // thêm 5 dấu cho nguyên âm
foreach ($tdx as $tkx) { // bắn ra các ký tự đã thêm dấu
$tg2x = thay_the($khongdau2, $tkx, $vtx[0]); // thay thế ký tự đó vào chuỗi gốc
$themdau4[$j] = $tg2x; $j++; // thêm ký tự đó vào mảng
}
}
}
}
///////////////////////////
$kq = array_merge($bot_kt,$them_kt,$dao_kt,$themdau2,$themdau3,$themdau4); // gộp các mảng vảo với nhau
//////////////////////////////////////
// loại bỏ những biến thể trùng với họ đang tồn tại và với họ ban đầu
$kqx=array(); $j=0;
// danh sách họ phổ biến
$hopb = array("nguyễn","trần","lê","phạm","huỳnh","võ","phan","trương","bùi","đặng","đỗ","ngô","vũ","hồ","hoàng","dương","đinh","đoàn","lâm","mai","trịnh","đào","cao","lý","hà","lưu","lương","châu","thái","tạ","tô","phùng","vương","văn","tăng","quách","lại","hứa","thạch","từ","diệp","chu","la","đàm","tống","giang","chung","triệu","tôn","kiều","trang","hồng","đồng","danh","lư","lữ","thân","kim","mã","bạch","liêu","tiêu","bành","âu","dư","khưu","sơn","tất","nghiêm","lục","phương","quan","mạc","lai","vòng","mạch","thiều","trà","đậu","nhan","lã","trình","ninh","vi","trầm","biện","hàng","chế","ôn","nhâm","thi","doãn","khổng","phù","đường","ông","viên","tào","cù","khương","phí","kha","ngụy","nông","ngũ","du","quang","lạc","liên","nhữ","lợi","giáp","ong","tiết","ung","quảng","long","sử","chiêm","cổ","sầm","vưu","cái","lăng","quản","vy","lường","điền","lu","khuất","khúc","phó","đới","cam","chương","nguyên","ma","đổ","thang","an","tân","thới","kiên","bồ","hạ","hàn","uông","lô","diệc","lao","tiền","vĩnh","liễu","giảng","bảo","chiêu","chí","đăng","khấu","thị","thượng","hoa","đổng","thiệu","hạp","hầu","tưởng","ưng","văng","lôi","đan","sỳ","phú","ô","cát","xa","khâu","lang","thẩm","tràn","trì","tiên","hoắc","mo","hán","diêu","trác","hình","điêu","chang","lộ","đại","cung","luân","sa","phún","hỷ","sú","nhiêu","bế","mang","ngọ","ca","lầu","thôi","ừng","cáp","sẩm","yên","ao","thòng","thích","cấn","giản","lộc","mông","công","lò","mao","chắng","tằng","hoặc","vân","vu","sái","hong","ký","bàng","đái","lồ","vỏ","hùng","kỳ","phòng","nghê","thiềm","dung","đôn","chiếng","lầm","chềnh","di","phi","thổ","khiếu","san","minh","thành","tsằn","quãng","lày","tòng","sằn","hòa","làu","thông","chim","huyền","quý","giao","mè","nìm","lượng","điểu");
foreach ($kq as $kqt) {
if (($kqt == $str) || (in_array($kqt, $hopb))) {unset($kqt);}
if (isset($kqt)) {$kqx[$j] = $kqt; $j++;}
}
// loại bỏ các giá trị trùng lặp
$ukq = array_unique($kqx);
return $ukq;
}