如何將數字轉換成口語中的文本串

概述

今天突發奇想, 寫一個將數字轉換成中文字符串的函數. 並不是將 1234 轉成 '1234' , 而是將 1234 轉成 '一千二百三十四'.

本來以爲很簡單, 寫下來之後發現還是有些坑的.

嘗試

因爲我是在寫完最終版本, 回過頭來整理的這篇文章, 所以中間很多嘗試的步驟會有所遺漏. 以下簡單整理一下. 如果不想看, 可以直接拉到最後, 看最終的成品.

第一次嘗試

在寫之前, 首先要尋找中文說話的規律嘛.

  1. 數字的念法: 零一二三四五六七八九
  2. 每一位都有一個對應的權重: 個十百千萬

所以我的初步想法是, 將數字的每一位都轉成中文然後拼上對應的權重, so easy. 以下爲 Python 實現:

# 數字中文
DIGIT_STR_LIST = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九']
# 權重中文
WIGHT_STR_LIST = ['', '十', '百', '千', '萬', '十萬', '百萬', '千萬', '億']

def num_to_str(num):
    # 保存每一位的內容
    result_list = []
    # 遍歷數字的每一位, 將數組轉列表並倒序遍歷
    for wight, digit in enumerate(reversed(list(str(num)))):
        digit = int(digit)
        digit_str = DIGIT_STR_LIST[digit] if digit < len(DIGIT_STR_LIST) else ''
        wight_str = WIGHT_STR_LIST[wight] if wight < len(WIGHT_STR_LIST) else ''
        # 結果拼接
        result_list.append(digit_str + wight_str)
    # 將結果倒序拼接
    result_list.reverse()
    return "".join(result_list)

OK, 寫的很流暢, 也很簡單, 嘗試一下.

  • 傳參: 1234 , 輸出: 一千二百三十四 . 很完美.
  • 五位數試一下: 54321. 輸出: 五萬四千三百二十一. nice
  • 六位數試一下: 654321 . 輸出: 六十萬五萬四千三百二十一. ???

有問題. 這裏問題很明顯了, 我將權重直接拼到了每一位的後邊, 而十萬直接拼上去明顯有問題. 正解應該是六十五萬四千三百二十一.

到這裏, 毫無疑問, 一開始思路就錯了, 需要重新改變一下思路了.

第二次嘗試

對於654321這個數字.

十萬位6沒有將十萬直接拼到後邊, 而是和萬位5連起來, 一起組成了六十五萬. 再多一個數字呢? 7654321, 就應該是七百六十五萬. 我貌似發現規律了, 把數字切分爲四個一組就可以了.

再看一下位數多一點的數字: 1-2345-6789. 中文是: 一億-二千三百四十五萬-六千七百八十九 嗯, 和我預想得一毛一樣. 大概懂了, 着手改進一下:

# 數字中文
DIGIT_STR_LIST = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九']
# 權重中文
WIGHT_STR_LIST = ['', '十', '百', '千']
# 分組後對應的中文
GROUP_STR_LIST = ['', '萬', '億', '兆']


def thousand_list_num_to_str(num_list: list) -> str:
    """
    將4位數字轉成字符串
    :param num_list: 數字列表, 長度不超過4. 索引和數字對應爲: 個十百千
    :return:
    """
    # 保存每一位的內容
    result_list = []
    # 遍歷數字的每一位, 將數組轉列表並倒序遍歷
    for wight, digit in enumerate(num_list):
        digit = int(digit)
        digit_str = DIGIT_STR_LIST[digit] if digit < len(DIGIT_STR_LIST) else ''
        wight_str = WIGHT_STR_LIST[wight] if wight < len(WIGHT_STR_LIST) else ''
        # 結果拼接
        result_list.append(digit_str + wight_str)
    # 將結果倒序拼接
    result_list.reverse()
    return "".join(result_list)


def num_to_str(num : int) -> str:
    """
    將數組裝成中文
    :param num:
    :return:
    """
    # 將數字切割爲每四個一組, 分別進行處理
    num_list = list(str(num))
    # 這裏爲了處理長度不是4整數倍的情況, 提前反轉.
    num_list.reverse()
    group_num_list = [num_list[i:i+4] for i in range(0, len(num_list), 4)]
    result_list = []
    # 遍歷每一組, 併產生對應中文輸出
    for group, num_list in enumerate(group_num_list):
        this_num_str = thousand_list_num_to_str(num_list)
        group_str = GROUP_STR_LIST[group] if group < len(GROUP_STR_LIST) else ''
        result_list.append(this_num_str + group_str)
    result_list.reverse()
    return ''.join(result_list)

OK! 現在已經可以應對剛纔的情況了. 試一下:

  • 654321 -> 六十五萬四千三百二十一
  • 321 -> 三百二十一
  • 120 -> 一百二十
  • 10101010 -> 一千百一十萬一千百一十 納尼????
  • 1000 -> 一千百一十 納尼???

很明顯, 問題出在thousand_list_num_to_str 這個函數.

四位數的時候, 0應該是要跳過的.

第三次嘗試

我們對thousand_list_num_to_str函數進行簡單的改進, 遇到零的時候直接跳過, 不進行處理. 改進後如下(只展示了部分改動的地方):

DIGIT_STR_LIST = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']


def thousand_list_num_to_str(num_list: list) -> str:
    """
    將4位數字轉成字符串
    :param num_list: 數字列表, 長度不超過4. 索引和數字對應爲: 個十百千
    :return:
    """
    # 保存每一位的內容
    result_list = []
    # 遍歷數字的每一位, 將數組轉列表並倒序遍歷
    for wight, digit in enumerate(num_list):
        digit = int(digit)
        # 0無輸出
        if digit is 0:
            continue
        digit_str = DIGIT_STR_LIST[digit] if digit < len(DIGIT_STR_LIST) else ''
        wight_str = WIGHT_STR_LIST[wight] if wight < len(WIGHT_STR_LIST) else ''
        # 結果拼接
        result_list.append(digit_str + wight_str)
    # 將結果倒序拼接
    result_list.reverse()
    return "".join(result_list)
    

OK, 再次嘗試.

  • 10101010 -> 一千一十萬一千一十 nice!
  • 100 -> 一百 nice!
  • 1210 -> 一千二百一十
  • 1201 -> 一千二百一 納尼??

這裏按照思維, 應該是輸出一千二百零一纔對. 繼續對thousand_list_num_to_str函數進行加工.

第四次嘗試

這裏thousand_list_num_to_str函數要將零輸出, 但是要考慮連續爲零的情況(前邊的100). 改動後代碼如下:

def thousand_list_num_to_str(num_list: list) -> str:
    """
    將4位數字轉成字符串
    :param num_list: 數字列表, 長度不超過4. 索引和數字對應爲: 個十百千
    :return:
    """
    # 保存每一位的內容
    result_list = []
    # 遍歷數字的每一位, 將數組轉列表並倒序遍歷
    for wight, digit in enumerate(num_list):
        digit = int(digit)
        if digit is 0:
            # 個位的0無輸出
            if wight is 0:
                continue
            # 連續0無輸出
            elif int(num_list[wight-1]) is 0:
                continue
            # 直接拼零
            result_list.append(ZERO_STR)
            continue
        digit_str = DIGIT_STR_LIST[digit] if digit < len(DIGIT_STR_LIST) else ''
        wight_str = WIGHT_STR_LIST[wight] if wight < len(WIGHT_STR_LIST) else ''
        #
        if digit is 0:
            wight_str = ''
        # 結果拼接
        result_list.append(digit_str + wight_str)
    # 將結果倒序拼接
    result_list.reverse()
    return "".join(result_list)

OK. 嘗試一下:

  • 100 -> 一百
  • 1201 -> 一千二百零一 nice
  • 101 -> 一百零一
  • 1000 -> 一千
  • 100000000 -> 一億萬 什麼鬼?

後邊怎麼多了一個?

第五次嘗試

有了處理0的經驗, so easy, num_to_str這個函數呀加上一個對0的處理就好了. 代碼如下(只展示了num_to_str函數):

def num_to_str(num : int) -> str:
    """
    將數組裝成中文
    :param num:
    :return:
    """
    # 將數字切割爲每四個一組, 分別進行處理
    num_list = list(str(num))
    # 這裏爲了處理長度不是4整數倍情況, 提前反轉.
    num_list.reverse()
    group_num_list = [num_list[i:i+4] for i in range(0, len(num_list), 4)]
    result_list = []
    # 遍歷每一組, 併產生對應中文輸出
    for group, num_list in enumerate(group_num_list):
        # 若是0, 跳過
        if int(''.join(num_list)) is 0:
            continue
        this_num_str = thousand_list_num_to_str(num_list)
        group_str = GROUP_STR_LIST[group] if group < len(GROUP_STR_LIST) else ''
        result_list.append(this_num_str + group_str)
    result_list.reverse()
    return ''.join(result_list)

再次進行嘗試:

  • 100000000 -> 一億 nice!!
  • 0 -> ??? 我的零呢?

第六次嘗試

這個判斷就粗暴了, 直接在num_to_str的入口處強制判一下0, 改動內容:

ZERO_STR = '零'
def num_to_str(num : int) -> str:
    if num is 0:
        return ZERO_STR
    ...

再來:

  • 0 ->
  • ...

經過我的一番測試, 基本完成.

總結

開始有這個想法的時候, 我想着會很簡單, 隨便寫寫咯. 但是當真正開始動手後, 才發現, 事情完全偏離了我的預期. 在寫的過程中, 初版只是個很簡單的版本, 但是在自己嘗試的過程中總是發現各種各樣的問題, 甚至有的時候解決了這個問題, 回頭一測, 發現原來已經改好的問題有出現了, 唉, 果然還是功力太淺啊. too young, too simple, sometimes naive.

我最終還算是磕磕絆絆的寫完了, 不過冥冥之中還是感覺有一些情況沒有考慮到, 無妨, 反正這不過是個一路填坑的過程, 再碰到問題, 改就完了.


至此, 代碼初步完成, 將完整代碼奉上:

# 數字中文
DIGIT_STR_LIST = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九']
# 權重中文
WIGHT_STR_LIST = ['', '十', '百', '千']
# 分組後對應的中文
GROUP_STR_LIST = ['', '萬', '億', '兆']
# 零
ZERO_STR = '零'


def thousand_list_num_to_str(num_list: list) -> str:
    """
    將4位數字轉成字符串
    :param num_list: 數字列表, 長度不超過4. 索引和數字對應爲: 個十百千
    :return:
    """
    # 保存每一位的內容
    result_list = []
    # 遍歷數字的每一位, 將數組轉列表並倒序遍歷
    for wight, digit in enumerate(num_list):
        digit = int(digit)
        if digit is 0:
            # 個位的0無輸出
            if wight is 0:
                continue
            # 連續0無輸出
            elif int(num_list[wight-1]) is 0:
                continue
            # 直接拼零
            result_list.append(ZERO_STR)
            continue
        digit_str = DIGIT_STR_LIST[digit] if digit < len(DIGIT_STR_LIST) else ''
        wight_str = WIGHT_STR_LIST[wight] if wight < len(WIGHT_STR_LIST) else ''
        #
        if digit is 0:
            wight_str = ''
        # 結果拼接
        result_list.append(digit_str + wight_str)
    # 將結果倒序拼接
    result_list.reverse()
    return "".join(result_list)


def num_to_str(num : int) -> str:
    """
    將數組裝成中文
    :param num:
    :return:
    """
    if num is 0:
        return ZERO_STR
    # 將數字切割爲每四個一組, 分別進行處理
    num_list = list(str(num))
    # 這裏爲了處理長度不是4整數倍情況, 提前反轉.
    num_list.reverse()
    group_num_list = [num_list[i:i+4] for i in range(0, len(num_list), 4)]
    result_list = []
    # 遍歷每一組, 併產生對應中文輸出
    for group, num_list in enumerate(group_num_list):
        # 若是0, 跳過
        if int(''.join(num_list)) is 0:
            continue
        this_num_str = thousand_list_num_to_str(num_list)
        group_str = GROUP_STR_LIST[group] if group < len(GROUP_STR_LIST) else ''
        result_list.append(this_num_str + group_str)
    result_list.reverse()
    return ''.join(result_list)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章