Xác định cấu trúc âm tiết tiếng Việt bằng Python

Về mặt lí thuyết thì cấu trúc âm tiết tiếng Việt có thể được miêu tả bằng nhiều cách khác nhau. Nhưng phổ biến nhất vẫn là cấu trúc hai bậc, trong đó bậc 1 gồm âm đầu (onset), vần (rhyme) và thanh điệu (tone), bậc 2 là các thành phần của vần, gồm: âm đệm (glide), âm chính (nucleus) và âm cuối (coda).

Hệ thống chữ quốc ngữ cũng khá tương thích với mô hình này, trừ một số trường hợp ngoại lệ.

Trong bài này, anh dùng Python để xác định cấu trúc âm tiết tiếng Việt thể hiện bằng chữ quốc ngữ. Kết quả trả về sẽ là một list gồm:

  • âm tiết
  • thanh điệu (từ 1 đến 6 tương ứng với không, huyền, hỏi, ngã, sắc, nặng)
  • âm đầu
  • âm đệm
  • âm chính
  • âm cuối
  • vần

Nếu âm tiết không hợp lệ thì sẽ trả về kết quả False.

import re
import vos
from pprint import pprint

def syllable_parser(syllable, verbose = False):

    """ Parse a Vietnamese syllable and 
        return a list of phonemes and the rhyme

    Parameters
    ----------
    syllable: string, required
        To be parsed syllable
    verbose: boolean, default False
        Show warnings or not

    Returns
    -------
    list
        If the syllable is valid
        [
            syllable,
            tone,
            onset,
            glide,
            nucleus,
            coda,
            rhyme
        ]
    False
        If the syllable is invalid
    """

    # Quick validation
    syllable = syllable.strip()

    pattern = "[aàảãáạăằẳẵắặâầẩẫấậeèẻẽéẹêềểễếệiìỉĩíịoòỏõóọôồổỗốộơờởỡớợuùủũúụưừửữứựyỳỷỹýỵ]"

    if re.search(rf"^([b-zđ]+)?{pattern}{{1,7}}([c-y]+)?$", syllable, re.I) is None:
        if verbose:
            print('Not look like a syllable')
        return False

    # Looking good, let's play!
    #

    # Correct I/Y and convert the string to lower case
    syllable = vos.vos_iy(syllable.lower())

    # Exceptional syllables
    if syllable == 'gịa':
        return ['gịa', '6', 'gi', '', 'ia', '', 'ia']
    if syllable == 'quốc':
        return ['quốc', '5', 'q', '', 'uô', 'c', 'uôc']

    # Which tone?
    #
    tones = {
        '2': '[àằầèềìòồờùừỳ]',
        '3': '[ảẳẩẻểỉỏổởủửỷ]',
        '4': '[ãẵẫẽễĩõỗỡũữỹ]',
        '5': '[áắấéếíóốớúứý]',
        '6': '[ạặậẹệịọộợụựỵ]',
    }
    # Unmarked tone
    tone = '1'

    for key, val in tones.items():
        if re.search(rf"{val}", syllable):
            tone = key
            break # The tone is determined

    # Which segmental phonemes?
    #
    # First, remove tone mark from the syllable
    syllable_ = vos.remove_tonemark(syllable)

    # Then make the regex pattern and search
    sets = {
        'onset': ['b', 'c', 'ch', 'd', 'đ', 'g', 'gh', 'gi', 'h',
                'k', 'kh', 'l', 'm', 'n', 'ng', 'ngh', 'nh', 'p',
                'ph', 'qu', 'r', 's', 't', 'th', 'tr', 'v', 'x'],

        'coda': ['c', 'ch', 'i', 'm', 'n', 'ng', 'nh', 'o', 'p', 't', 'u', 'y']
    }

    onsets = ('|').join(sets['onset'])
    glides = '[uo]'
    nucleuses = '[aăâeêioôơuưy]|[iy]ê|ươ|[iyuư]a|[uô]ô|oo'
    codas = ('|').join(sets['coda'])

    pattern = re.compile(rf"\b({onsets})?({glides})?({nucleuses})({codas})?\b")

    s = pattern.search(syllable_)

    # The search result is not None
    if s:
        # The segmental phonemes
        _onset = s[1] or ''
        _glide = s[2] or ''
        _nucleus = s[3]
        _coda = s[4] or ''

        # But there are some exceptions
        #
        # Q-
        if _onset == 'qu':
            _onset = 'q'
            _glide = 'u'
            # QUÔ-
            if _nucleus == 'ô':
                return False

        # GI-
        if _onset == 'g' and _nucleus in ['i', 'ia', 'iê']:
            _onset = 'gi'
            # GIA-
            if _nucleus == 'ia':
                _nucleus = 'a'

        # -UA, -UÔ-
        if _glide == 'u' and _nucleus in ['a', 'ô'] and _onset != 'q':
            _glide = ''
            _nucleus = 'u' + _nucleus

        # -OO-
        if _glide == 'o' and _nucleus == 'o':
            _glide = ''
            _nucleus = 'oo'
        
        # -UI, -OI
        if _glide != '' and _nucleus == 'i':
            _coda = _nucleus
            _nucleus = _glide
            _glide = ''

        # Optional warning
        if tone not in ['5', '6'] and _coda in ['c', 'ch', 'p', 't'] and verbose:
            print("Invalid tone mark")

        # The rhyme is a combinaton of the last three segmental phonemes
        rhyme = _glide + _nucleus + _coda

        # End result
        return [syllable, tone, _onset, _glide, _nucleus, _coda, rhyme]

    # If None searched, the syllable is invalid
    return False

Trong hàm trên, anh dùng hai hàm riêng:

Chạy thử:

for syllable in ['gì', 'giêng', 'gịa', 'giạ', 'giáo', 'nghiêng', 
    'thuyền', 'ăn', 'oà', 'ủa', 'quốc', 'quộc', 'cuộc',
    'muynh', 'toảt', 'time', 'quyét', 'international', 'tì m']:

    print(syllable.upper())
    pprint(syllable_parser(syllable, True))
    print('-' * 7)

cho ra kết quả:

GÌ
['gì', '2', 'gi', '', 'i', '', 'i']
-------
GIÊNG
['giêng', '1', 'gi', '', 'iê', 'ng', 'iêng']
-------
GỊA
['gịa', '6', 'gi', '', 'ia', '', 'ia']
-------
GIẠ
['giạ', '6', 'gi', '', 'a', '', 'a']
-------
GIÁO
['giáo', '5', 'gi', '', 'a', 'o', 'ao']
-------
NGHIÊNG
['nghiêng', '1', 'ngh', '', 'iê', 'ng', 'iêng']
-------
THUYỀN
['thuyền', '2', 'th', 'u', 'yê', 'n', 'uyên']
-------
ĂN
['ăn', '1', '', '', 'ă', 'n', 'ăn']
-------
OÀ
['oà', '2', '', 'o', 'a', '', 'oa']
-------
ỦA
['ủa', '3', '', '', 'ua', '', 'ua']
-------
QUỐC
['quốc', '5', 'q', '', 'uô', 'c', 'uôc']
-------
QUỘC
False
-------
CUỘC
['cuộc', '6', 'c', '', 'uô', 'c', 'uôc']
-------
MUYNH
['muynh', '1', 'm', 'u', 'y', 'nh', 'uynh']
-------
TOẢT
Invalid tone mark
['toảt', '3', 't', 'o', 'a', 't', 'oat']
-------
TIME
False
-------
QUYÉT
False
-------
INTERNATIONAL
Not look like a syllable
False
-------
TÌ M
Not look like a syllable
False
-------

Cho đến nay thì anh vẫn chưa biết ứng dụng cái này làm gì ngoài việc tìm âm tiết cùng vần.

Một bình luận

  1. […] đầu, vần và thanh điệu thì tương đối dễ, vì hôm trước anh đã giới thiệu cách xác định cấu trúc âm tiết […]