PAT A1082:Read Number in Chinese

特别复杂的一个模拟题,事实证明还是分析规律使用枚举法最简单

题目内容

Given an integer with no more than 9 digits, you are supposed to read it in the traditional Chinese way. Output Fu first if it is negative. For example, -123456789 is read as Fu yi Yi er Qian san Bai si Shi wu Wan liu Qian qi Bai ba Shi jiu. Note: zero (ling) must be handled correctly according to the Chinese tradition. For example, 100800 is yi Shi Wan ling ba Bai.

Input Specification:

Each input file contains one test case, which gives an integer with no more than 9 digits.

Output Specification:

For each test case, print in a line the Chinese way of reading the number. The characters are separated by a space and there must be no extra space at the end of the line.

Sample Input 1:

-123456789

Sample Output 1:

Fu yi Yi er Qian san Bai si Shi wu Wan liu Qian qi Bai ba Shi jiu

Sample Input 2:

100800

Sample Output 2:

yi Shi Wan ling ba Bai

题目要点

本题 25 分,这道题是非常复杂,因为提供的样例很少,规则也需要自己根据生活经验去分析,最后还要考虑些边界条件。对问题模拟的好坏直接影响代码编写的复杂程度,因此需要静心分析,能发现规律的利用迭代、循环,没有规律的,枚举出各种条件、情况,梳理清晰。

本文用Python实现,但是基本思路和其它语言差别不大。另外,这道题也可以用正则表达式处理,但是输入的值最多是9位,而且问题简化后完全可以枚举出几种情况,所以不建议用正则表达式。

首先,每一位的数字对应一个汉字,0~9一共10个数字用digit[]表示;从低位数起,每个4位都是以“(个)、十、百、千”为后缀在数字之后,用place[]表示;每个4位在读完后还要加个后缀,如“万”、“亿”,用suffix[]表示。因此,我们可以将整个数字按4位切分成若干段,每一段译出数字、“十百千”,封装成一个函数。调用一次后根据这是第i个段对应加上“万”、“亿”的后缀。最终合并起来,如果是负数再在头部加Fu即可。

其次,最为棘手的是“零”的处理,什么时候读“零”,什么时候只读一个,什么时候不读,这是这道题最难的地方。好在我们将数字按4位分割成段后问题简单多了,只用考虑最多4位的情况:

  • 如果4位全部是0,如0000,那么完全不译,函数返回空值。比如12,0000读作十二万1,0000,5000读作一亿五千
  • 如果零出现在末尾,如1200读作一千二百1000读作一千,那么末尾零的数字、在的“十百千”位也不译;
  • 如果零出现在中间或开头,要看是否有连续的零:
    • 如果只有一个零,如2305读作二千零五0234读作零二三四(因为这是从数字中取出四位,所以允许开头有零),那么译出“零”,但是不译“十百千”位;
    • 如果有两个及以上连续个零,如2003读作二千零三0005读作零五,那么连续的零只译出一个即可,同样的,不译“十百千”位。

这样就基本将问题考虑完全,那么如何实现上述对零的处理?我这里的repl()函数是将完全不译的零替换为一个字符,如*repl()函数比较取巧,首先替换完末尾零,然后考虑连续零只留一个,先将000替换成**0,再将00替换成*0,注意顺序不可以颠倒。最终返回字符串。

solve()函数中,按位遍历字符串时读到该位是*continue跳过这一步循环,否则就按照正常的数字译出,如果数字是零,那么只译“零”不译“十百千”。遍历结束作为当前段,返回列表,

需要注意的是,输入的值可能是有前导零,如002,所以要先处理下输入的值。还要考虑边界情况,如,输入0测试点3),会被上述算法当作末尾零不译,所以要在程序入口先判断下值是否为0,如果是直接输出ling,如果不是,再调用上述的算法。

这里提供一些额外的测试样例,以供参考

// 输入
-880808080
// 输出
Fu ba Yi ba Qian ling ba Shi Wan ba Qian ling ba Shi

// 输入
800000000
// 输出
ba Yi

// 输入
80000008
// 输出
ba Qian Wan ling ba

// 输入
0
// 输出
ling

// 输入
002
// 输出
er

// 输入
1010010
// 输出
yi Bai ling yi Wan ling yi Shi

// 输入
1000010
// 输出
yi Bai Wan ling yi Shi

// 输入
100000000
// 输出
yi Yi

AC 源代码

from math import ceil

def repl(s):
    sp = s.rstrip('0')
    s = sp + '*' * (len(s)-len(sp))
    s = s.replace('000', '**0').replace('00', '*0')
    return s

def solve(part):
    if part == '0000':
        return []
    part = repl(part)
    words = []
    for i, dig in enumerate(part[::-1]):
        if dig == '*':
            continue
        dig = int(dig)
        if dig:
            words.append( (digit[dig] + ' ' + place[i]).strip() )
        else:
            words.append( digit[dig] )
    return words


digit = ['ling', 'yi', 'er', 'san', 'si', 'wu', 'liu', 'qi', 'ba', 'jiu']
place = ['', 'Shi', 'Bai', 'Qian']
suffix = ['', 'Wan', 'Yi']
result = []

number = str(int(input()))

if number.startswith('-'):
    negative = True
    number = number.lstrip('-')
else:
    negative = False

if number != '0':
    for i in range( ceil(len(number)/4) ):
        part = number[-(1+i*4):-(5+i*4):-1][::-1]
        part = solve(part)
        if part:
            result.append(suffix[i])
        result.extend(part)

    if negative:
        result.append('Fu')

    print(' '.join(result[::-1]).strip())
else:
    print(digit[0])

更多思考

使用正则表达式处理这道题的“零”问题更容易,而且还可以得到连续零的起讫下标,将字符串转换为列表就可以依据得到的下标直接替换。

查找替换末尾零

[0]{1,}$
import re
part = '1034000'
part = re.sub(r'[0]{1,}$', '*', part)
>> '1034***'

查找替换连续零

(0){2,}
import re
part = '1034007009'
find = re.finditer(r'(0){2,}', part)
for f in find:
    print(f.span())
>> (4, 6)
>> (7, 9)

参考来源

https://www.liuchuo.net/archives/2204

https://zhuanlan.zhihu.com/p/346321610