Dùng regex để biết chuỗi có phải là một âm tiết tiếng Việt

Vì đã tạo được danh sách tất cả các âm tiết có thể có trong tiếng Việt nên anh đã dùng nó luôn để đối chiếu. Chứ biểu thức chính quy như này trông cực kì cồng kềnh và có vẻ không đáng tin. Nhưng nếu không muốn tải danh sách để đối chiếu (nhất là ở phía front-end) thì regex sẽ là một giải pháp tương đối tiện, dù có thể sẽ nhận nhầm một số trường hợp.

Như thường lệ, anh bắt đầu bằng Python và nếu cần dùng cho giao diện người dùng thì có thể bảo ChatGPT chuyển sang Javascript.

import re

def is_syllable(sample):

    sample = sample.strip()
    
    if len(sample) > 7:
        return False

    # Initials
    init = r"(b|c|ch|d|đ|g|gh|gi|h|k|kh|l|m|n|ng|ngh|nh|p|q|ph|r|s|t|th|tr|v|x)"
    init_no_q = r"(b|c|ch|d|đ|g|gh|gi|h|k|kh|l|m|n|ng|ngh|nh|p|ph|q|r|s|t|th|tr|v|x)"
    
    # Single nuclears
    nuclear0 = r"([aàảãáạăằẳẵắặâầẩẫấậeèẻẽéẹêềểễếệiìỉĩíịoòỏõóọôồổỗốộơờởỡớợuùủũúụưừửữứựyỳỷỹýỵ]|o[oòỏõóọ]|ô[ôồổỗốộ])"

    # Dual nuclears
    nuclear2 = r"([iìỉĩíịuùủũúụưừửữứự]a)" # ia, ua, ưa
    nuclear3 = r"([iy][êềểễếệ]|ư[ơờởỡớợ]|u[ôồổỗốộ])" # iê, yê, ươ, uô
    
    # Tone-restricted nuclei
    nuclear4 = r"[aàảãăằẳẵâầẩẫeèẻẽêềểễiìỉĩoòỏõôồổỗơờởỡuùủũưừửữyỳỷỹ]" # Tone 1, 2, 3, 4

    # Ending
    ending = r"(c|ch|i|m|n|ng|nh|o|p|t|u|y)"
    ending_no_y = r"(c|ch|i|m|n|ng|nh|o|p|t|u)"

    # Valid syllables
    x110 = fr"(qu[aàảãáạ]|{init}?(o[aàảãáạeèẻẽéẹ]|u[eèẻẽéẹêềểễếệơờởỡớợyỳỷỹýỵ]|u[yỳỷỹýỵ]a))"
    x010 = fr"({init_no_q}?({nuclear0}|{nuclear2}))"
    x011 = fr"({init}?({nuclear0}|{nuclear3}|o[oòóỏõóọ]){ending}|[uùủũúụ]{ending_no_y})"
    x111 = fr"({init}?((o[aàảãáạăằẳẵắặeèẻẽéẹ]|u[ăằẳẵắặâầẩẫấậeèẻẽéẹêềểễếệơờởỡớợyỳỷỹýỵ])|uy[aêềểễếệ]|qu[aàảãáạ]){ending})"

    comp = fr"({x110}|{x010}|{x011}|{x111})"

    # Invalid syllables
    not1 = r"^((k|gh|ngh)[^heèẻẽéẹêềểễếệiìỉĩíị]|q[^u]).*$"
    not2 = (
        fr"(hh|{nuclear4}[ptc]"
        fr"|[eèẻẽéẹ](i|nh|u|y)"
        fr"|[^g][iìỉĩíị](i|o|y)"
        fr"|[oòỏõóọ](nh|u|y)"
        fr"|[uùủũúụ](ch|nh|o|u))"
    )
    not3 = r"(c|ng)[eèẻẽéẹêềểễếệiìỉĩíị]|g[eèẻẽéẹêềểễếệ]|gi[iìỉĩíị]"

    # Final checks
    if re.match(fr"^{comp}$", sample, re.IGNORECASE) \
        and not re.match(not1, sample, re.IGNORECASE) \
        and not re.match(fr"(.*)?{not2}", sample, re.IGNORECASE) \
        and not re.match(fr"{not3}", sample, re.IGNORECASE):
        return True

    return False

# Example usage
s = 'nguyêng'
print(f"{s} is {'valid' if is_syllable(s) else 'NOT valid'}")