Còn rất nhiều thứ cần làm và đáng làm nhưng chủ đề này đã quá dài. Mình dừng ở đây. Các việc còn lại, mình sẽ mở chủ đề khác. Kết quả cập nhật năm nay, mình đã tự đánh giá trong các bài trước cả rồi. Bài này dành để trình bày chi tiết phương án hay nhất. Đó là "đế đạo" [#60].
Nhưng trước hết, xin nói 1 câu về kỹ thuật đánh giá. Các con số không biết nói dối, chỉ hiềm là đôi khi chúng ta không biết đọc số cho đúng mà thôi. Theo thống kê, tỷ lệ tử vong của Mỹ trong các năm từ 2015 đến nay lần lượt là 84, 85, 86, 87, 88, 89 nhân mạng/10 nghìn dân. Nếu xem tỷ lệ tử vong mỗi năm thêm 1 mạng ấy là do một nhân tố X nào đó chưa biết chứ không phải là ngẫu nhiên, do X != COVID, kết luận đúng, dẫu khó tin, là số người Mỹ chết vì COVID trong năm 2020 không đáng kể (1).
Theo Bảng 1 [bài #2], có 34 phụ âm đầu, trong đó 1 không có chữ cái nào (--), 22 có một chữ cái (c, b, d, đ, f, g, h, j, k, l, m, n, p, q, r, s, t, v, w, x, y, z), 10 có hai chữ cái (ch, dz, gh, gi, kh, ng, nh, ph, th, tr) và 1 có ba chữ cái (ngh). Nếu chúng được sắp lại theo thứ tự tăng dần của độ dài, thì độ dài của một phụ âm đầu P nào đó với mã số p là
Code:
(0 < p) + (22 < p) + (32 < p)
Cần phải làm bao nhiêu phép tính để tính được giá trị biểu thức trên? Hỏi rõ ngớ ngẩn, 5 phép tính chứ còn bao nhiêu nữa (3 phép so sánh và 2 phép cộng). Câu hỏi của mình sẽ bớt "ngớ ngẩn" đi nếu xem xét biểu thức như thế với nhiều số hạng hơn, chẳng hạn:
Code:
(a_1 < p) + (a_2 < p) + (a_3 < p) + ... + (a_128 < p)
Học sinh tiểu học nào đã từng giải bài toán "tìm đồng xu giả lẫn trong các đồng xu thật bằng một cái cân đòn" cũng biết không cần làm đến 255 phép tính. Mình chỉ còn nhớ được nó cỡ log(128), nhưng cái "log(128)" ấy chính xác là bao nhiêu thì mình không biết. (Mong ai đó có thể bổ túc cho mình bằng một đáp số chính xác.)
Như vậy, để xét nghiệm một số có thuộc về một tập trị S nào đó hay không, nếu S gọn (S là 1 khoảng) thì tốt nhất, nhưng nếu S không gọn, cụ thể, S có 2^n khoảng rời nhau rải rác khắp nơi, thì cũng không có vấn đề gì to tát cả. Số phép tính bất quá cũng chỉ tăng lên gấp n lần mà thôi.
Nếu có thể coi "bá đạo" nhằm tốc độ (độ trễ, tính bằng số xung nhịp) thì "đế đạo" có thể coi là nhằm kích cỡ (bộ nhớ, tính bằng bit). Nó đi đến đích ấy bằng cách khai thác triệt để trật tự ngữ âm. Dẫu biết làm thế là đôi khi phải bỏ qua trật tự chính tả. Nghĩa là phải hy sinh tính gọn của vài tập trị nào đó.
Thuật toán giải mã có thể cài đặt mô phỏng bằng phần mềm và "bộ nhớ" khi ấy bao gồm cả mã lệnh và dữ liệu. Nhưng để đánh giá đúng thì không nên tính như thế. Chỉ tính dữ liệu thôi. Lý do là khi cài đặt (mô phỏng) bằng phần mềm, những tính toán phức tạp, gồm nhiều nhánh if...else..., sinh rất nhiều mã lệnh so với kích cỡ bảng (mảng), nhưng khi cài đặt (thực thi) bằng phần cứng (vi mạch), các tính toán phức tạp, gồm nhiều phép tính AND, OR, XOR, NOT,... cũng vẫn sinh rất ít gate, chả đáng là bao so với kích cỡ bảng (mảng).
Giải mã "đế đạo" phức tạp hơn "vương đạo" và "bá đạo". Tính phức tạp ấy phản ánh tự nhiên tính tinh vi của mô hình âm tiết truyền thống [2] so với mô hình âm tiết chính thống [#2] và, do đó, tính rắc rối của phép biến đổi giữa hai mô hình này khi được dùng để mã hóa [#60]. Trình bày lần này, để trực quan hơn, mình giải mã C* thẳng ra mã A2 (2), tô điểm định dạng thường lệ của bảng mã C* bằng kết quả trong A2 và giá trị từng byte của bộ nhớ ROM dùng cho phép giải mã ấy (3).
Nhắc lại, trong mã A2, mỗi âm vần là một xâu 2 ký tự. Để giảm bộ nhớ, ở đây, ký tự đầu được xác định bằng tính toán, chỉ có ký tự sau là tra bảng mà thôi. Nhờ thế, mỗi phần tử của mảng âm vần chỉ còn 1 byte.
Nói rõ hơn, năm lớp âm vần ox, ux, ưx, ix, yx là xâu 2 ký tự trong mã A2 hiện thời và, bây giờ, cả lớp âm vần ôx cũng thế (4). Hàm giải mã sẽ phải dùng cấu trúc điều khiển if...else..., switch...case..., biểu thức có điều kiện hoặc mạch điện tử tương đương để tính ra ký tự o, ô, u, ư, i, y, còn bảng (mảng) chỉ dùng để tra ký tự x (tức ký tự thể hiện xâu x của các chữ cái Việt), cũng như ký tự duy nhất ở sáu lớp âm vần còn lại, là ax, ăx, ex, êx, ơx, âx (Bảng 15).
Bảng 15. (Phương án có tính minh hoạ) 192 âm vần của mã C* đã được giải ra mã A2. Ví dụ, 0x00 là ao trong C* và được giải ra ao trong A2 (tức 1 ký tự, với mã số nào đó), 0x50 là ua trong C* và giải ra u-a (tức một xâu 2 ký tự), 0x80 là oao/uao trong C* và giải ra o-ao/u-ao (tức một xâu 2 ký tự với 2 giá trị khả dĩ chỉ khác nhau ở ký tự đầu), 0x30 là iu/uiu trong C* và giải ra i-u/u-iu (tức một xâu 2 ký tự với 2 giá trị khả dĩ khác nhau ở cả ký tự cuối). Ở dòng thứ ba của mỗi ô là 1 byte của mảng giải mã âm vần, thường là ký tự duy nhất hoặc giá trị duy nhất (nếu có) của ký tự thứ hai của mã số (A2). Để đỡ rối mắt, bảng chỉ hiển thị những byte bất quy tắc (khác thường lệ đó). Ví dụ, mảng[0x00] = ao, mảng[0x50] = a, mảng[0x70] = --, mảng[0x80] = ao và mảng[0x30] = iu.
Code:
0_ 1_ 2_ 3_ 4_ 5_ 6_ 7_ 8_ 9_ A_ B_
========================================================================================
_0 ao eo âu i-u ư-u u-a ư-ơu ô-- o-ao o-eo u-âu u-yu
u-iu u-ao u-eo
iu
----------------------------------------------------------------------------------------
_1 ai e ây i-- ư-- u-ôi ư-ơi ô-i o-ai o-e u-ây u-y
u-i u-ai u-e
i
----------------------------------------------------------------------------------------
_2 am em ân i-m ư-m u-ôm ư-ơm ô-m o-am o-em u-ân u-ym
u-im u-am u-em
im
----------------------------------------------------------------------------------------
_3 ap ep ât i-p ư-p u-ôp ư-ơp ô-p o-ap o-ep u-ât u-yp
u-ip u-ap u-ep
ip
----------------------------------------------------------------------------------------
_4 an en ư-n i-n i-êm u-ôn ư-ơn ô-n o-an o-en u-ưn u-yn
u-in y-êm u-an u-en
ưn in n
----------------------------------------------------------------------------------------
_5 at et ư-t i-t i-êp u-ôt ư-ơt ô-t o-at o-et u-ưt u-yt
u-it y-êp u-at u-et
ưt it t
----------------------------------------------------------------------------------------
_6 ang eng ư-ng i-nh ô-ông u-ông ư-ơng ô-ng o-ang o-eng u-ưng u-ynh
u-inh u-ang u-eng
ưng inh nh
----------------------------------------------------------------------------------------
_7 ac ec ư-c i-ch ô-ôc u-ôc ư-ơc ô-c o-ac o-ec u-ưc u-ych
u-ich u-ac u-ec
ưc ich ch
----------------------------------------------------------------------------------------
_8 au êu ơ a y-- i-êu o-- u-- o-au u-êu u-ơ o-a
y-êu u-au
a
----------------------------------------------------------------------------------------
_9 ay ê ơi i-a ư-i ư-a o-i u-i o-ay u-ê u-ơi u-ya
u-ay
ia
----------------------------------------------------------------------------------------
_A ăm êng âng i-êng i-ng ơm o-m u-m o-ăm u-êng u-âng u-yêng
y-êng u-ing u-ăm
iêng ing
----------------------------------------------------------------------------------------
_B ăp êc âc i-êc i-c ơp o-p u-p o-ăp u-êc u-âc u-yêc
y-êc u-ic u-ăp
iêc ic
----------------------------------------------------------------------------------------
_C ăn ên ơn i-ên o-ong êm o-n u-n o-ăn u-ên u-ơn u-yên
y-ên u-ăn
iên
----------------------------------------------------------------------------------------
_D ăt êt ơt i-êt o-oc êp o-t u-t o-ăt u-êt u-ơt u-yêt
y-êt u-ăt
iêt
----------------------------------------------------------------------------------------
_E ăng ênh ơng anh -- âm o-ng u-ng o-ăng u-ênh u-ơng o-anh
u-ăng
ng
----------------------------------------------------------------------------------------
_F ăc êch ơc ach -- âp o-c u-c o-ăc u-êch u-ơc o-ach
u-ăc
c
----------------------------------------------------------------------------------------
Bảng mã C* lần này đã cập nhật một tý so với phiên bản ban đầu [#60].
Bảng âm vần có 192 ô, nhưng mảng âm vần không cần đến 192 byte. Nó đã được sắp xếp sao cho có thể cắt bỏ đoạn cuối (nghĩa là sao cho bất cứ ký tự nào trong đoạn ấy cũng đều tính được), từ 0x62 = 98 trở đi, nên thực tế chỉ còn 98 byte.
Nhắc lại, trong mã A2, mỗi phụ âm đầu là một xâu 2 ký tự. Tuy vậy, tương tự trên, phụ âm đầu cũng có thể giải mã với một mảng có ít hơn 32 phần tử và mỗi phần tử có ít hơn 2 ký tự (byte). Chẳng hạn, với các phụ âm đầu được xếp đặt lại như Bảng 16 (cảnh báo: đây là "tà đạo",) dễ tìm được một công thức giải mã (tính được cả 2 ký tự) bằng một mảng như Hình 17, vốn chỉ có 23 byte.
Bảng 16. (Phương án có tính minh họa) 32 phụ âm đầu của mã C* đã giải ra mã A2. Ví dụ, 1 là b trong C* và được giải ra b trong A2 (tức 1 ký tự, với một mã số nào đó); 0x1C là ng/ngh trong C* và được giải ra n-g trong A2 (tức một xâu có 2 ký tự); 0x14 là g/gh trong C* và được giải ra g/g-h trong A2 (tức một xâu có hai giá trị khả dĩ và có lần lượt 1, 2 ký tự).
Code:
_0 _1 _2 _3 _4 _5 _6 _7 _8 _9 _A _B _C _D _E _F
==================================================================================
0_ -- b đ f h j l m q s v w x y c k
1_ p n t d g r z c-h k-h p-h n-h t-h n-g t-r d-z g-i
g-h
Hình 17. Mảng byte phụ trợ giải mã phụ âm đầu từ C* ra A2 theo Bảng 16. Mỗi phần tử (byte) là một ký tự của mã A2.
Code:
b đ f h j l m q s v w x y c k p n t d g r z i
Không cần phải tốn byte nào cho độ dài phụ âm đầu. Độ dài có thể tính theo công thức ở đầu bài này. Vì âm đầu chỉ có không hơn 2 ký tự, công thức đó chỉ còn 3 phép tính (5).
Vậy, tiêu dùng bộ nhớ hết thảy là 98 + 23 = 121 byte.
Chỉ còn một vấn đề nữa thôi. Mảng 98 byte trên không hề có ký tự yu, chỉ có iu và, nói chung, không hề có các ký tự dạng yx mà chỉ có biến thể ix của nó. Ta biết rằng tương phản với các mã C*, B+, A3,... trong các mã tự điển A2, A3',... không có quan hệ số học - luận lý nào giữa hai ký tự, nói riêng, giữa ix,yx, ngoại trừ quan hệ thứ tự tầm thường, như ix < yx. Thế thì chuỗi tính toán giải mã uyu -> uiu -> u-iu -> u-yu chẳng hạn, tài nào làm được?
Dễ thôi. Không có quan hệ nào thì ta sẽ tạo ra quan hệ.
Mã A2 hiện có 121 ký tự [#57]. Thêm 7 ký tự ia, iêc, iên, iêng, iêt, yc, yng nữa, nó sẽ có 121 + 7 = 128 ký tự, vẫn nằm trong ngân sách 7 bit. Hình 18 chỉ ra vị trí 7 ký tự mới trong bảng ký tự, qua đó gợi ra một hệ thức mới hình thành ở từng đôi ký tự (ix,yx), độc lập với x, tương tự như hệ thức ở từng đôi chữ cái viết thường và viết hoa trong bảng ASCII. Bảy ký tự mới được dùng làm trung gian giải mã, nhưng không xuất hiện trong mã số đã giải xong. Theo nghĩa này, vị trí của chúng trong bảng ký tự có thể xem như bỏ trống. Bảng 19 liệt kê đủ 128 ký tự.
Hình 18. Hai đoạn trích bảng ký tự của mã A2 đã bổ sung 7 ký tự, cụ thể, đoạn ix và đoạn yx. Giá trị 7 ký tự mới thêm được hiển thị bằng các dấu - để ngụ ý 7 vị trí đó xem như bỏ trống.
Code:
i,--,ic,ich,---,---,----,---,im,in,ing,inh,ip,it,iu
y,ya,--,ych,yêc,yên,yêng,yêt,ym,yn,---,ynh,yp,yt,yu
Giờ đây, ta có thể làm phép biến đổi ký tự ix -> yx bằng một phép tính, tương tự như phép biến đổi to_lower của ASCII. Đó là phép tính gì, xin để cho bạn đọc tự tìm.
Bảng 19. Bảng ký tự mã A2, phiên bản mới. Bảy vị trí đánh dấu bằng --- là 7 ký tự mới. Hai ký tự ôông, ôôc đã được loại bỏ (6). Hai chỗ trống để lại đã di chuyển về 0x07 và 0x2B để align hai cặp (ang,anh), (êc,êch) và mọi cặp khác tương tự, nhằm tạo thuận lợi cho một phép biến đổi ký tự có tiềm năng sử dụng (7).
Code:
0_ 1_ 2_ 3_ 4_ 5_ 6_ 7_
=============================================
_0 -- ăm d êng im ong ơt y
_1 a ăn đ ênh in ô ơu ya
_2 ac ăng e êp ing ôc p ---
_3 ach ăp ec êt inh ôi q ych
_4 ai ăt em êu ip ôm r yêc
_5 am âc en f it ôn s yên
_6 an âm eng g iu ông t yêng
_7 ân eo h j ôp u yêt
_8 ang âng ep i k ôt ư ym
_9 anh âp et --- l ơ ưc yn
_A ao ât ê ic m ơc ưn ---
_B ap âu ich n ơi ưng ynh
_C at ây êc --- ng ơm ưt yp
_D au b êch --- nh ơn v yt
_E ay c êm --- o ơng w yu
_F ăc ch ên --- oc ơp x z
"Quái chiêu" vừa rồi cũng là "chiêu" cuối, để cho chủ đề này một cái kết đẹp.
____________________
(1) Không có ý xem thường COVID. Mình biết nó rất nguy hiểm và mình đã tiêm vắc-xin.
(2) Thay đổi chỉ ở cách thực hiện thuật toán, chứ thuật toán không thay đổi. Người thực hiện vẫn chọn cách mã hoá và tuỳ theo cách ấy, vẫn chọn cách giải mã, với các mã trung gian đã được trình bày trước đây. Đơn cử, người thực hiện vẫn có 2 lựa chọn cho chữ yêu là mã hóa bằng --yêu hoặc bằng y-yêu và, nếu chọn cách sau, thì trình tự giải mã (trong ý niệm) vẫn là C* -> B+ -> A3 -> A2, qua đó, nói riêng, mã âm vần iêu/yêu giải ra êu (thay vì y-êu) khi phụ âm đầu là y và giải ra êu (thay vì i-êu) khi phụ âm đầu là gi.
(3) Cấu trúc dữ liệu trong suốt loạt bài này là một đồ thị có hướng không chu trình, nhưng đã cụ thể hóa (để tối ưu hóa) đến mức thấp nhất, là một mảng. Song, ngay cả cấu trúc cụ thể này cũng chỉ nhằm minh hoạ tư tưởng và phương pháp. Mảng có thể xếp đặt theo nhiều cách khác nhau, cho những chi phí thời gian và bộ nhớ khác nhau.
(4) Năm lớp ix, yx, ưx, ux, ox khởi phát từ các âm vần có âm đệm (wx) và âm vần có nguyên âm đôi (iêw, ươw, uôy). Lớp ôx được thêm vào đó do tương tự lớp ox.
(5) Và chỉ tốn 2 xung nhịp. Trong suốt loạt bài, tiêu chí thời gian chạy là cho trường hợp xấu nhất.
(6) Thật ra, mã A2 không cần đến 128 ký tự. Nó đã hơi khác phiên bản cũ [#57]: lớp âm vần ôx giờ không còn là một ký tự mà là là một xâu, cụ thể, xâu ô-x. Nói riêng, ôông được mã hóa bằng ô-ông và tương tự, ôôc bằng ô-ôc. Ký tự ôông, ôôc do đó trở thành thừa. Hai chỗ trống có thể cho người cài đặt nhiều tự do hơn và Bảng này là ví dụ.
(7) Ngoài ra, bảng ký tự của mã A2 (và nói chung, bất cứ mã nén hậu tố nào có bảng ký tự theo thứ tự từ điển) còn có đặc điểm là với xâu chữ cái a bất kỳ, hai ký tự có dạng ac, ach, nếu có, đi liền nhau trong bảng ký tự và hai ký tự ang, anh cũng thế. Quan hệ đó cho một phép biến đổi ký tự có tiềm năng sử dụng, dẫu chưa được dùng, là (ang,ac) -> (anh,ach).