Mục từ đầu tiên trong từ điển tiếng Việ

Tạo tất cả các âm tiết tiếng Việt có thể có và đọc được

Giãn cách mãi cũng chán, chán mãi cũng ngán, anh đành đi học Python – một thứ mà cách đây 15, 16 năm anh đã định học.

“Học phải đi đôi với hành”. Vì thế anh mang bài tập cách đây cũng tầm 1,5 thập kỉ ra làm lại:

Căn cứ theo hệ thống chữ quốc ngữ, anh/chị hãy tạo tất cả các âm tiết tiếng Việt có thể có ở dạng chữ quốc ngữ và đương nhiên là phải đọc được.

Giải pháp tạo lập âm tiết

Phải nói ngay là bài tập này tuy cũ nhưng mới chỉ làm dở dang.

Trước đây anh tạo ra tất cả các tổ hợp (gồm 4 thành phần: âm đầu + âm đệm + âm chính (có thanh điệu) + âm cuối). Cách này sinh rất nhiều tổ hợp lỗi. Vì thế nó lâm vào tình trạng bế tắc.

Lưu ý: Các âm vị trong bài này sẽ ghi bằng cách thể hiện của nó trong chữ quốc ngữ.

Còn bây giờ anh lấy từng âm chính (có thanh điệu) và lần lượt kết hợp với các các thành tố còn lại nhưng có giới hạn khả năng kết hợp ngay từ ban đầu.

Ví dụ: Âm chính “a” thì không kết hợp với các âm đầu “k, gh, ngh” và âm đệm “u”. Hay như nguyên âm đôi “ưa” thì âm đệm zero và âm cuối zero.

Dữ liệu đầu vào sẽ được đưa vào một biến dict đa chiều, mỗi thành phần nó có cấu trúc như sau:

'key': {
    'initial': ['', 'b', 'c', '...'],
    'schwa':   ['', 'o'],
    'nuclear': ['a', 'à', 'ả', 'ã', 'á', 'ạ'],
    'ending':  ['', 'c', 'ch', 'i', '...']
}

Theo đó, với từng âm chính thì sẽ có các danh sách âm đầu, âm đệm và âm cuối riêng.

Việc tổ hợp các âm tiết sẽ thực hiện qua một loạt các vòng lặp. Trong quá trình này, anh cũng tiến hành lọc theo lô-gích. Ví dụ: loại bỏ các trường hợp có thanh điệu không, huyền, hỏi, ngã mà âm cuối là p, t, c, ch.

Căn cứ xác định khả năng kết hợp

Việc xác định khả năng kết hợp vừa căn cứ theo lí thuyết về hệ thống âm vị tiếng Việt, vừa căn cứ vào thực tế.

Âm đầu b, m, ph, v, p

Một số quan điểm lí thuyết cho rằng những âm môi này không kết hợp với âm đệm (u, o) nhưng thực tế một số từ vay mượn vẫn đọc được dễ dàng: boa, moa, phuy v.v.

Âm chính ơ

Trong trường hợp âm chính ơ với âm đệm u và âm cuối không zero thì thực tế chỉ xuất hiện rất hi hữu trong phương ngữ Nam Bộ (quới, quớt). Hiện tại anh tạm bỏ qua kết hợp này, chỉ giữ lại trường hợp âm đầu “q”.

Ngoài ra còn một số quy luật hoặc lựa chọn khả năng kết hợp khác. Các bạn có thể xem bài làm dưới đây để biết cụ thể.

Bài làm

Đây là bài làm bằng Python của anh.

# Vietnamese Syllables Generator
#
# Author: Phạm Thành Long
# Website: https://luom.tv/
#
# Generating a list of theoretically Vietnamese syllables
# based on the Quốc ngữ writing system.
# 
# TODO: "ơ" with schwa sound "u" and non-zero ending sounds
#

from icu import Collator, Locale # pip install --no-binary=:pyicu: pyicu

collator = Collator.createInstance(Locale('vi_VN.UTF-8'))

# Defining sound blocks

blocks = {
    # A
    'v_a1': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'qu', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['', 'o'],
        'nuclear': ['a', 'à', 'ả', 'ã', 'á', 'ạ'],
        'ending':  ['', 'c', 'ch', 'i', 'm', 'n', 'ng', 'nh', 'o', 'p', 't', 'u', 'y']
    },
    # Ă
    'v_a2': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'qu', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['', 'o'],
        'nuclear': ['ă', 'ằ', 'ẳ', 'ẵ', 'ắ', 'ặ'],
        'ending':  ['c', 'm', 'n', 'ng', 'p', 't']
    },
    # Â 
    'v_a3': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'qu', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['', 'u'],
        'nuclear': ['â', 'ầ', 'ẩ', 'ẫ', 'ấ', 'ậ'],
        'ending':  ['c', 'm', 'n', 'ng', 'p', 't', 'u', 'y']
    },
    # E
    'v_e1': {
        'initial': ['', 'b', 'ch', 'd', 'đ', 'g', 'gh', 'gi', 'h', 'k', 'kh', 'l', 'm', 'n', 'ng', 'ngh', 'nh', 'p', 'ph', 'qu', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['', 'o'],
        'nuclear': ['e', 'è', 'ẻ', 'ẽ', 'é', 'ẹ'],
        'ending':  ['', 'c', 'm', 'n', 'ng', 'o', 'p', 't']
    },
    # Ê  
    'v_e2': {
        'initial': ['', 'b', 'ch', 'd', 'đ', 'g', 'gh', 'gi', 'h', 'k', 'kh', 'l', 'm', 'n', 'ng', 'ngh', 'nh', 'p', 'ph', 'qu', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['', 'u'],
        'nuclear': ['ê', 'ề', 'ể', 'ễ', 'ế', 'ệ'],
        'ending':  ['', 'ch', 'm', 'n', 'nh', 'p', 't', 'u']
    },
    # I
    'v_i': {
        'initial': ['', 'b', 'ch', 'd', 'đ', 'g', 'gh', 'h', 'k', 'kh', 'l', 'm', 'n', 'ngh', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['i', 'ì', 'ỉ', 'ĩ', 'í', 'ị',],
        'ending':  ['', 'ch', 'm', 'n', 'nh', 'p', 't', 'u']
    },
    # O
    'v_o1': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['o', 'ò', 'ỏ', 'õ', 'ó', 'ọ'],
        'ending':  ['', 'c', 'i', 'm', 'n', 'ng', 'p', 't']
    },
    # OO
    'v_oo1': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['oo', 'oò', 'oỏ', 'oõ', 'oó', 'oọ'],
        'ending':  ['c', 'ng']
    },
    # Ô
    'v_o2': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['ô', 'ồ', 'ổ', 'ỗ', 'ố', 'ộ'],
        'ending':  ['', 'c', 'i', 'm', 'n', 'ng', 'p', 't']
    },
    # ÔÔ
    'v_oo2': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['ôô', 'ôồ', 'ôổ', 'ôỗ', 'ôố', 'ôộ'],
        'ending':  ['ng']
    },
    # Ơ1 (kết hợp với âm đệm zero)
    # ngoài trường hợp âm đầu "q" thì thực tế không thấy trường hợp có cả âm đệm lẫn âm cuối không zero
    'v_o3': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'qu', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['ơ', 'ờ', 'ở', 'ỡ', 'ớ', 'ợ'],
        'ending':  ['', 'c', 'i', 'm', 'n', 'ng', 'p', 't']
    },
    # Ơ2 (kết hợp với âm đệm /-w-/, âm cuối zero)
    'v_o4': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['u'],
        'nuclear': ['ơ', 'ờ', 'ở', 'ỡ', 'ớ', 'ợ'],
        'ending':  ['']
    },
    # U
    'v_u1': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['u', 'ù', 'ủ', 'ũ', 'ú', 'ụ'],
        'ending':  ['', 'c', 'i', 'm', 'n', 'ng', 'p', 't']
    },
    # Ư
    'v_u2': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['ư', 'ừ', 'ử', 'ữ', 'ứ', 'ự'],
        'ending':  ['', 'c', 'i', 'm', 'n', 'ng', 'p', 't', 'u']
    },
    # Y1
    'v_y1': {
        'initial': ['', 'b', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'q', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['u'],
        'nuclear': ['y', 'ỳ', 'ỷ', 'ỹ', 'ý', 'ỵ'],
        'ending':  ['', 'ch', 'm', 'n', 'nh', 'p', 't', 'u']
    },
    # Y2 (ngoại lệ, cho một số từ Hán-Việt)
    'v_y2': {
        'initial': [''],
        'schwa':   [''],
        'nuclear': ['y', 'ỳ', 'ỷ', 'ỹ', 'ý', 'ỵ'],
        'ending':  ['']
    },
    # IA (gi được khai thành g)
    'v_ia': {
        'initial': ['', 'b', 'ch', 'd', 'đ', 'g', 'gh', 'h', 'k', 'kh', 'l', 'm', 'n', 'ngh', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['ia', 'ìa', 'ỉa', 'ĩa', 'ía', 'ịa'],
        'ending':  ['']
    },
    # IÊ (gi được khai thành g)
    'v_ie': {
        'initial': ['b', 'ch', 'd', 'đ', 'g', 'gh', 'h', 'k', 'kh', 'l', 'm', 'n', 'ngh', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['iê', 'iề', 'iể', 'iễ', 'iế', 'iệ'],
        'ending':  ['c', 'm', 'n', 'ng', 'p', 't', 'u']
    },
    # UA
    'v_ua': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['ua', 'ùa', 'ủa', 'ũa', 'úa', 'ụa'],
        'ending':  ['']
    },
    # UÔ
    'v_uo': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['uô', 'uồ', 'uổ', 'uỗ', 'uố', 'uộ'],
        'ending':  ['c', 'i', 'm', 'n', 'ng', 'p', 't']
    },
    # ƯA
    'v_wa': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['ưa', 'ừa', 'ửa', 'ữa', 'ứa', 'ựa'],
        'ending':  ['']
    },
    # ƯƠ
    'v_wo': {
        'initial': ['', 'b', 'c', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   [''],
        'nuclear': ['ươ', 'ườ', 'ưở', 'ưỡ', 'ướ', 'ượ'],
        'ending':  ['c', 'i', 'm', 'n', 'ng', 'p', 't', 'u']
    },
    # YA
    'v_ya': {
        'initial': ['', 'b', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'q', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['u'],
        'nuclear': ['ya', 'ỳa', 'ỷa', 'ỹa', 'ýa', 'ỵa'],
        'ending':  ['']
    },
    # YÊ1
    'v_ye1': {
        'initial': ['', 'b', 'ch', 'd', 'đ', 'g', 'gi', 'h', 'kh', 'l', 'm', 'n', 'ng', 'nh', 'p', 'ph', 'q', 'r', 's', 't', 'th', 'tr', 'v', 'x'],
        'schwa':   ['u'],
        'nuclear': ['yê', 'yề', 'yể', 'yễ', 'yế', 'yệ'],
        'ending':  ['c', 'm', 'n', 'ng', 'p', 't']
    },
    # YÊ2 (ngoại lệ)
    'v_ye2': {
        'initial': [''],
        'schwa':   [''],
        'nuclear': ['yê', 'yề', 'yể', 'yễ', 'yế', 'yệ'],
        'ending':  ['c', 'm', 'n', 'ng', 'p', 't', 'u']
    }
}

# Initial sounds which not followed by 'o' or 'u' (/-w-/ phoneme)
initial_not_followed_by_w = ['c', 'k', 'gh', 'ngh', 'qu']  # and 'b', 'm', 'ph', 'v' in some theory

# Ending sounds in tone 5 or tone 6 syllables only
ending_in_tone56_only = ['p', 't', 'c', 'ch']

# "Schwa" sounds
schwa_chars = ['o', 'u']

# e, ê nuclear
e_ee = ['e', 'è', 'ẻ', 'ẽ', 'é', 'ẹ', 'ê', 'ề', 'ể', 'ễ', 'ế', 'ệ']

# Syllable list initiated with "quốc"
syllables = ['quốc']

# Generating syllables
for key in blocks:
    sounds = blocks[f'{key}']
    for i in sounds['initial']:
        for w in sounds['schwa']:
            if (i in initial_not_followed_by_w and w in schwa_chars):
                continue
            for k, n in enumerate(sounds['nuclear']):
                if i in ['ng', 'g'] and w == '' and n in e_ee:
                    continue
                for e in sounds['ending']:
                    if (k < 4 and e in ending_in_tone56_only):
                        continue
                    syllables.append(i + w + n + e)
                    #print(i + w + n + e)

# Sorting syllables
syllables = sorted(syllables, key=collator.getSortKey)

# Check for duplicates
duplicates = []
for i, w in enumerate(syllables):
    if syllables[i] == syllables[i-1]:
        #print('\t', i, syllables[i], end="")
        duplicates.append(syllables[i-1])
        syllables.pop(i-1)

# Write to file
filename = 'LuomTV-Vietnamese-Syllables-Auto-Generated.txt'
with open(filename, 'w') as f:
    f.writelines("%s\n" % l for l in syllables)
    
f.close()

# Display all
#for syllable in syllables:
#    print(syllable)

print('Duplicated syllable(s): ', duplicates)
print('\nNumber of generated syllables: ', len(syllables))
print('\nSaved in:', filename)

Còn đây là danh sách âm tiết được tạo ra từ đoạn mã trên:

LuomTV-Vietnamese-Syllables-Auto-Generated.txt

Ứng dụng

Danh sách này gồm 19.453 âm tiết (cập nhật 30/8/2021), dài gấp 3 lần và không thiếu âm nào so với danh sách âm tiết có nghĩa hiện nay. Mặc dù đã kiểm tra nhiều lần nhưng chắc là danh sách này vẫn còn sai sót. Nhưng cũng không quan trọng lắm. Bởi vì, như đã nói từ đầu, anh làm danh sách này là để thực hành món Python.

Ngoài ra, danh sách này để làm gì nữa thì cũng chưa nghĩ ra. Nhưng biết đâu lại có ích với ai đó :v

2 responses

  1. […] chục ngày trước, để thực hành môn Python, anh đã viết một đoạn mã tạo ra tất cả các âm tiết tiếng Việt có thể có và đọc được. Nhưng chưa biết để làm […]

  2. […] trong những bài thực hành đầu tiên là dùng Python để tạo tất cả các âm tiết tiếng Việt có thể có và đọc được. Rồi tôi nhớ ra danh sách âm tiết tiếng Việt mà mình đã đăng trên luom.tv năm […]

Bình luận

Website này sử dụng Akismet để hạn chế spam. Tìm hiểu bình luận của bạn được duyệt như thế nào.