python之人民币金额打印

1.贴题

题目来自PythonTip
人民币金额打印


银行在打印票据的时候,常常需要将阿拉伯数字表示的人民币金额转换为大写表示,现在请你来完成这样一个程序。

在中文大写方式中,0到10以及100、1000、10000被依次表示为: 零 壹 贰 叁 肆 伍 陆 柒 捌 玖 拾 佰 仟 万

以下的例子示范了阿拉伯数字到人民币大写的转换规则:

1 壹圆

11 壹拾壹圆

111 壹佰壹拾壹圆

101 壹佰零壹圆

-1000 负壹仟圆

1234567 壹佰贰拾叁万肆仟伍佰陆拾柒圆

现在给你一个整数a(|a|<100000000), 请你打印出人民币大写表示.

例如:a=1

则输出:壹圆

注意:请以Unicode的形式输出答案。提示:所有的中文字符,在代码中直接使用其Unicode的形式即可满足要求,中文的Unicode编码可以通过如下方式获得:u’壹’。

注意:代码无需声明编码!!不要在代码头部声明文件编码,否则会导致语法错误!

Note:数据已于2013-11-19日加强,原来通过的代码可能不能再次通过。


2.说明

预警!!!
这道题个人认为已经算很难了,完全超出了基础题的范畴,如果是初学者建议先不要挑战,或者想要试试水平的可以尝试,但是要做好花大量时间的准备。

预计时间2小时起步,上不封顶。

先简单解释下思路。
首先分析需求:
1. 最多不超过八位数的整数
2. 因为可能出现负数所以需要单独判断是否为负数
3. 数字0-9分别对应中文的十个汉字
4. 八位整数大致可以分为前后两段各四段,原理类似
5. 如果两个非零数字间出现零则需要添加零,但是只能添加一个,不可以添加单位
6. 首位不会出现0,所以不需要考虑该情况
7. 输出是最后以“圆”结尾

3.参考代码

参考了大神pythonwood的代码。
分别给出 一气呵成法分段讨论法 的解说。

一气呵成法

def f(n): #定义主要字符串生成函数
    num = [u"零", u"壹", u"贰", u"叁", u"肆", u"伍", u"陆", u"柒", u"捌", u"玖"] #将数字放在列表num中
    cre = [u"圆", u"拾", u"佰", u"仟", u"万", u"拾", u"佰", u"仟"] #将单位放在列表cre中,根据不同位顺序排列是关键,可以重复,在后面使用下标时可以更方便

    s = "" #定义一个空字符串备用
    L = [int(i) for i in str(n)] #将数字转化为字符串用来遍历每次取一个数再转化为数字,以便后面用于下标

    t = 0 #将初始的t设为0
    while t<len(L): #在t小于L长度时始终循环,保证遍历每一个数
        if t>0 and L[t-1]==0: #一开始t=0所以不会进到里面。之后还有一个循环要不满足后面的循环但是满足这一条件说明:t位上的数不为0但是t位前面那位(从左向右遍历)上的数为0
            s += num[0] #在s字符串最后添加“零”
        s += (num[L[t]] + cre[len(L)-t-1]) #非特殊情况下,首先取数字对应的中文数字,然后取该位上对应的单位,添加到字符串
        t += 1 #将t加一,准备判断下一位的数字
        while t<len(L) and L[t]==0: #从第二位(t=1)开始,先判断该位上数字是否为0,如果为0,则进入到该循环。注意到该循环中t会继续往上+1,也就是一直为零的话会一直在此循环里出不去,直到不为零才会跳出循环回到上层循环的if处
            if t+4==len(L)-1 and L[t]==0: #当找到万位并且万位上的数字为0时
                s += cre[-4] #在s字符串末尾增加“万”字
            t += 1 #其他情况跳过不操作,将t加一判断下一位
    if s[-1] != cre[0]: #所有循环结束后,如果字符串最后一个字符不为“圆”
        s += cre[0] #则在末尾增加“圆”
    return s #返回s字符串


def p(n): #定义判断是不是负数的函数
    return f(n) if n>=0 else u'负'+f(-n) #如果是正数则不做处理否则返回在首部添加“负”的字符串

print(p(a)) #对a执行函数

分段讨论法

def f(n): #定义主要函数返回字符串
    if n==0: #如果输入为0
        return '零' #则返回“零”
    w0 = ['零','壹','贰','叁','肆','伍','陆','柒','捌','玖'] #定义数字列表
    r = [] #定义空列表用于存放产生的字符(上一方法中使用了字符串)
    L = [int(i) for i in str(n)] #将数字每次取一个存放至L

    t = 0 #初始化t
    while t<len(L): #当t小于L长度时
        if t>0 and L[t-1]==0: #同上,上一位为零且本位不为零时
            r.append('零') #添加“零”
        r.append(w0[L[t]]+['仟','佰','拾',''][4-len(L)+t]) #正常情况下添加数字和单位
        t += 1 #t加一准备下一数的处理
        while t<len(L) and L[t]==0: #当本位为0时,进入循环,直到不为零才退出
            t += 1 #什么也不做移至下一数
    return ''.join(r) #将生成的字符串列表连接以字符串形式输出

def _p(n): #分段讨论加单位的函数
    d, m = divmod(n, 10000) #将输入分为两段,4位为一段
    e, n = divmod(d, 10) #对前四位判断个位(即原数中的万位)是否为0
    if m != 0 or d == 0: #如果前四位为0或后四位不为零(输入不大于四位数或输入大于四位数但万位以下不全为0)
        r = f(m)+'圆' #对后四位执行字符串函数并在末尾加“圆”
    elif d !=0: #如果前四位不为0(输入大于四位数且万位以下全为0)
        r = '圆' #返回字符串内容为“圆”
    return  r if d==0 else f(d)+'万'+r if (m>=1000 and n != 0) or m ==0 else f(d)+'万零'+r #如果输入不大于四位数返回r字符串;如果输入大于四位数且万位不为零或后四位为零,对前四位执行函数加上“万”后与r拼接;如果输入大于四位数且万位为零且后四位不为零,对前四位执行函数加上“万零”后与r拼接

def p(n): #判断是否为负数的函数
    return _p(n) if n>=0 else '负'+_p(-n)

print(p(a)) #对a执行函数

方法总结
主要技巧

  • 将数字和单位放在不同的列表中,方便计算下标
  • 单位列表按个十百千万等位排序存放,可以重复,方便计算下标
  • 巧妙利用for和while的不同处(for为固定次数,从开始到结束,即使在里面进入别的循环不会影响下一个循环值;while则是判断条件,如果有一部分满足条件的情况需要对循环变量做操作可以很方便的实现,跳出循环后循环变量已经改变),比如下面的代码就会发生错误。
for t in range(len(L)):
        if t>0 and L[t-1]==0:
            s += num[0]
        s += (num[L[t]] + cre[len(L)-t-1])
        while t<len(L) and L[t]==0:
            if t+4==len(L)-1 and L[t]==0:
                s += cre[-4]
            t += 1
  • 本位不为零且上位为零的构思也很巧妙(首位不可能为0,第二位为零但是首位不为零所以不符合要求,第三位不为零而第二位为零才符合要求)
  • 对万位特别处理,如果万位为零则要额外添加“万”字
  • 如果后四位全为0,就会缺失“圆”字,要补上
  • 负数另外处理

分类讨论
- 小于四位时(后四位函数+“圆”)
- 大于四位且后四位为零且万位不为零时(前四位函数+“万”+“圆”)
- 大于四位且后四位为零且万位为零时(前四位函数+“万”+“圆”)
- 大于四位且后四位不为零且万位不为零时(前四位函数+“万”+后四位函数+“圆”)
- 大于四位且后四位不为零但万位为零时(前四位函数+“万零”+后四位函数+“圆”)

4. 改错练习

练习1 来自jevonswang

b = str(a)

minus=''
if b[0]=='-':
    minus = '负'
    b=b.replace('-','')

money = ['零','壹','贰','叁','肆','伍','陆','柒','捌','玖']
pos = ['','拾','佰','仟','万','拾','佰','仟']
ans=''

l = len(b)

zero = False
for i in range(l):
    if(int(b[i])!=0):
        if(zero):
            zero = False
            ans = ans + '零'
        ans = ans + money[int(b[i])]+pos[l-1-i]     
    else:
        if i == l-5:
            ans = ans + '万'
            zero = False
        else:
            zero = True

ans = minus+ans+'圆'
print ans.decode("utf8")

问题
1. 输入0显示 圆(正确应为 零圆)
2. 10001000 显示 壹仟万壹仟圆(正确应为 壹仟万零壹仟圆)

修改后代码

b = str(a)

minus=''
if b[0]=='-':
    minus = '负'
    b=b.replace('-','')

money = ['零','壹','贰','叁','肆','伍','陆','柒','捌','玖']
pos = ['','拾','佰','仟','万','拾','佰','仟']
ans=''

l = len(b)

zero = False
for i in range(l):
    if(int(b[i])!=0):
        if(zero):
            zero = False
            ans = ans + '零'
        ans = ans + money[int(b[i])]+pos[l-1-i]     
    else:
        if i == l-5:
            ans = ans + '万' #去掉将zero置为False的代码,解决问题2
        else:
            zero = True

if ans == '': #增加一个判断条件,如果执行函数后字符串还是空的,说明输入为0
    ans = '零' + ans #返回“零”,解决问题1
ans = minus+ans+'圆'
print(ans)

练习2 来自kupors

x = [u'零', u'壹', u'贰', u'叁', u'肆', u'伍', u'陆', u'柒', u'捌', u'玖']
y = [u'个', u'拾', u'佰', u'仟']

def sign(n):
    if n < 0:
        return '负'
    else:
        return ''

def zh(n):
    n = abs(n)
    l = 4
    s = ''
    while l > 0:
        num = n % 10
        s = x[num] + y[4 - l] + s
        n //= 10
        l = l - 1
    return s

def cut0(s):
    s = s.replace(u'零仟', u'零')
    s = s.replace(u'零佰', u'零')
    s = s.replace(u'零拾', u'零')
    s = s.replace(u'零个', '')
    while u'零零' in s:
        s = s.replace(u'零零', u'零')
    s = s.replace(u'个', '')
    return s

def p(a):
    if a == 0:
        return(u'零圆')
    elif abs(a) < 10000:
        s = sign(a) + cut0(zh(a)) + u"圆"
        s = s.replace(u'零圆', u'圆')
        if s[0] == u'零':
            s = s[1:]
        return s
    else:
        a1 = abs(a) // 10000
        a2 = abs(a) % 10000
        s1 = cut0(zh(a1)) + u"万"
        if s1[0] == u'零':
            s1 = s1[1:]
        s2 = cut0(zh(a2)) + u"圆"
        s1 = s1.replace(u'零万', u'万')
        s2 = s2.replace(u'零圆', u'圆')
        return sign(a) + s1 + s2

print(p(a))

问题
1. 输入-1显示 负零壹圆(正确应为 负壹圆)
2. 10001000 显示 壹仟万壹仟圆(正确应为 壹仟万零壹仟圆)

修改后代码

x = [u'零', u'壹', u'贰', u'叁', u'肆', u'伍', u'陆', u'柒', u'捌', u'玖']
y = [u'个', u'拾', u'佰', u'仟']

def sign(n):
    if n < 0:
        return '负'
    else:
        return ''

def zh(n):
    n = abs(n)
    l = 4
    s = ''
    while l > 0:
        num = n % 10
        s = x[num] + y[4 - l] + s
        n //= 10
        l = l - 1
    return s

def cut0(s):
    s = s.replace(u'零仟', u'零')
    s = s.replace(u'零佰', u'零')
    s = s.replace(u'零拾', u'零')
    s = s.replace(u'零个', '')
    while u'零零' in s:
        s = s.replace(u'零零', u'零')
    s = s.replace(u'个', '')
    return s

def p(a):
    if a == 0:
        return(u'零圆')
    elif abs(a) < 10000:
        s = cut0(zh(a)) + u"圆"
        s = s.replace(u'零圆', u'圆')
        if s[0] == u'零':
            s = s[1:]
        s = sign(a) + s #将符号的添加移至取出零的步骤之后,解决问题1
        return s
    else:
        a1 = abs(a) // 10000
        a2 = abs(a) % 10000
        s1 = cut0(zh(a1)) + u"万"
        if s1[0] == u'零':
            s1 = s1[1:]
        s2 = cut0(zh(a2)) + u"圆"
        s1 = s1.replace(u'零万', u'万')
        s2 = s2.replace(u'零圆', u'圆')
        s3 = sign(a) + s1 + u'零' + s2 if a1%10 == 0 and a2 != 0 else sign(a) + s1 + s2 #增加了判断万位为0且后四位不为零的情况,并加上零
        s3 = s3.replace(u'零零', u'零') #前后分别加上零的情况导致出现'零零',暴力将其替换,解决问题2
        return s3

print(p(a))

5.其他写法

暴力破解法 来自paul

num_list=u"零壹贰叁肆伍陆柒捌玖"
wei_list=u'万圆拾佰仟万拾佰仟亿拾佰仟'
def GetDX(num):
    s=''
    flag=0
    if num<0:
        num=-num
        flag=1
    wei=len(str(num))
    for i in str(num):
        s=s+num_list[int(i)]+wei_list[wei]
        wei=wei-1
    s=Modify(s)
    if num==0:
        s=u'零圆'
    if flag:
        s=u'负'+s
    return s
def Modify(s):
    if u'零仟' in s:
        s=s.replace(u'零仟',u'零')
    if u'零佰' in s:
        s=s.replace(u'零佰',u'零')
    if u'零拾' in s:
        s=s.replace(u'零拾',u'零')
    while u'零零' in s:
        s=s.replace(u'零零',u'零')
    if u'零万' in s:
        s=s.replace(u'零万',u'万')
    if u'零圆' in s:
        s=s.replace(u'零圆',u'圆')
    return s
ans=GetDX(a)
print ans #.deocde('utf-8')

6.总结

  • 首先要对银行系统的程序员们致敬。。。从来没有想过输出中文的数字是如此之难!今后我会更加珍惜的!
  • 将大问题分解为小问题,一个一个解决的思想很重要
  • 基础知识不扎实就会各种碰壁,比如说本题对for和while的区别认识深的才能够想到巧妙的办法
  • 没有好的办法时,记住分类讨论
  • 遇到完美解决不了的时候,尝试一下暴力法
  • 将测试用例分一下类,每个类别都试试,去发现问题
  • 除了自己写代码,看人家的代码学习和找错也是快速提高的好方法

猜你喜欢

转载自blog.csdn.net/weixin_41980474/article/details/80212571