目录
效果展示及解决方案 问题分析 解决方案 个人感悟
效果展示及解决方案
下方左图为问题展示,右图为解决效果
适用范围:中文英文特殊字符的混合字符串
解决代码
def is_chinese(uchar):
# 判断当前字符是否为中文字符
return uchar >= u'\u4e00' and uchar <= u'\u9fa5'
def func(ustring):
# 输入一个字符串,输出一个字符串
# 输出字符串的显示宽度为指定宽度(非域宽)
width = 20 * 3
# 将20改为你想要的显示宽度(默认全角)
# 注意显示宽度应当大于字符串中字符个数
for uchar in ustring:
width -= 3 if is_chinese(uchar) else 2
return ustring + ' ' * int(width / 2) if width % 2 == 0 else ustring + '\u3000' + ' ' * int((width - 3) / 2)
# inf 为储存信息的字典,其他数据结构亦可(仅做展示),确保name处为字符串
for name, score in inf.items():
name_new = func(name)
print(" name:{} \t scores:{:<8}".format(name_new, score))
不想深究的话到这里就可以了,后面就是解决的过程。其实也没写多少东西,主要是其他文章在某些点上写的比我好,但是看完了的话就相当于自己踩了一遍坑,我就写了自己的真实感悟,提供的方案的适用领域更广,想学具体的知识点的话请划走。
问题分析
1.背景
因为某种原因(又是社团的活),要绘制一个榜单,然后就遇到了这个中文不对齐问题,我研究(瞎琢磨)了几个小时,发现这个东西简直就是一个巨坑,我断言(丢脸),这个问题永远只能优化而得不到解决,除非另辟蹊径或者文明重启!
嘴上说的狠,但是ddl在催(哭)。我搜索到了好多解决方案,但是都不满意,下文我会一一列举,最后xzh学长(不亏是学长)提供了一个思路,然后我自己写了一个解决方案,最后算是解决了,于是决定写这篇博客记录一下。
2.解决过程
我的活是制作一个榜单,榜单上是协会成员自己起的用户名,每个人都有自己的累计成绩,最后进行排名后打印出来榜单。我将所有信息读取到文档之后,利用字典统计了所有人的信息,然后排序得到一个列表,然后打印信息,然后问题就出现了,中文不对齐!
下方inf为二维列表变量,每个成员储存用户名和用户分数两个数据,代码中就不注释了。
for i in range(len(new_inf)):
name = func(new_inf[i][0])
score = int(new_inf[i][1])
print("#{:<5} name:{:<20} ".format(i + 1, new_inf[i][0]), end="")
print("socres:{:<8} complete:{:.0%}".format(score, (score/(num_eg * 100))))
然后我没有意识到问题的严重行,直接用了制表符\t,然后情况有所好转但未解决
for i in range(len(new_inf)):
name = func(new_inf[i][0])
score = int(new_inf[i][1])
print("#{:<5} name:{:<20} ".format(i + 1, new_inf[i][0]), end="")
print("\tsocres:{:<8} complete:{:.0%}".format(score, (score/(num_eg * 100))))
然后脑子抽了,想到了更新符\r,然后谜之操作后得到了下图
for i in range(len(new_inf)):
name = func(new_inf[i][0])
score = int(new_inf[i][1])
print("#{:<5} name:{:<20} ".format(i + 1, new_inf[i][0]), end="")
print("\r\t\t\t\t\t\t\t\tsocres:{:<8} complete:{:.0%}".format(score, (score/(num_eg * 100))))
其实效果也达到了只是牺牲了一些东西(bushi)
然后就看看网上的大神们的方案呗,这个时候才发现问题的复杂性,大神们解决的问题没有我这个复杂,无可奈何的情况下看看有没有库吧,然后真发现了一个tabulate库,然后看了看效果图,算了好丑,而且还是封装的,最后决定自己写,写的时候想着将所有字符的视宽调成一样不就行了吗,然后得到了下图,目的达成成了,但是还是不美观。
def half_all(uchar):
# 单个字符 半角转全角
swap = ord(uchar)
if swap < 0x0020 or swap > 0x7e: # 全角字符直接返回
return uchar
# 除了空格其他的全角半角的公式为: 半角 = 全角 - 0xfee0
# 特判
if swap == 0x0020:
swap = 0x3000
else:
swap += 0xfee0
return chr(swap)
def func1(ustring):
# 把字符串全角转半角
return "".join([half_all(uchar) for uchar in ustring])
for i in range(len(new_inf)):
name = func1(new_inf[i][0])
score = int(new_inf[i][1])
print("#{:<5} name:{:\u3000<20} ".format(i + 1, name), end=" ")
print("\tsocres:{:<8} complete:{:.0%}".format(score, (score/(num_eg * 100))))
然后就去问学长看看实际工程环境是怎么解决这个问题的,然后得到了一个处理思路,于是写了一下,还是有一点问题,补救一下,perfect!
def is_chinese(uchar):
# 判断当前字符是否为中文字符
return uchar >= u'\u4e00' and uchar <= u'\u9fa5'
def func(ustring):
# 将输入字符串调节为输出宽度为20个全角字符的宽度
# 记录输入字符串的宽度,遇到中文字符加3,遇到其他字符加2
num = 0
for uchar in ustring:
if is_chinese(uchar):
num += 3
else:
num += 2
# 计算需要补充的宽度,进行补充并返回补充后的字符串
add = 60 - num
if add % 2 == 0:
return ustring + ' ' * int(add / 2)
else:
return ustring + '\u3000' + ' ' * int((add - 3) / 2)
for i in range(len(new_inf)):
name = func(new_inf[i][0])
score = int(new_inf[i][1])
print("#{:<5} name:{:<20} ".format(i + 1, name), end="")
print("\tsocres:{:<8} complete:{:.0%}".format(score, (score/(num_eg * 100))))
解决方案
具体的知识点(或者说是方案的思路)
首先利用文档处理数据和基础的数据结构我不多说了,先说一下问题出现的原因。不论利用print的哪种格式化形式,其中格式化输出宽度指的是输出字符串的长度,而不是打印出来的视觉宽度(允许我使用这个词),但是我们知道python有好多字符格式,其中两个全角字符和三个半角字符视觉宽度相同(谁设的这个离谱参数),还有一些特殊字符视觉宽度只能以像素为单位进行计算(我也懒得数几个像素了),这就导致明明输出的一组字符串长度相同但是不对齐,所以一切的一切都是由于字符格式不统一。
在意识到print格式化没用后,我先用了制表符和刷新符,然后太久没用导致了开始的乌龙。我的设想是前面不管了,直接设定后面的打印位置,当然我做游戏的经验告诉我如果是贴图的话这个思路是没问题的,不深究了。详见前面的三处错误。
然后博客园有位大神(反正比我厉害)提供了一个思路,将所用字符全部转为全角格式,然后我就试了一下,这个注意将print格式化自动填充设为全角空格(也就是‘\u3000’),问题是解决了,但是看的很别扭。对了,这个要自学一下万维码,不是asc11码。
关于制表这个其实是有第三方库的,但是这个效果一言难尽,可以自己搜索使用tabulate库。
然后xzh学长提供思路:记录字符中各种字符的出现次数,然后预设自己的预期宽度,直接自己填充空格后返回就行,于是我的代码里面先预设了视觉宽度,然后遵循全角三格半角两格的原则减去,最后在返回的时候补上对应的空格,详见前方代码,这时候其实还是有问题的,因为特殊字符的视觉宽度真的就是以像素为单位的,加一个\t吧。
个人感悟
打印小小一张表这么费劲,要不是处理的数据太多,榜单每天更新我真想手画。写下这篇博客记录我逝去的几个小时,不过进一步理解了字符的相关知识还是挺有意义的。
相关知识点:
1.print格式化输出
2.各类字符的显示问题
3.各类字符的编码关系
看到这里了,就点个赞吧,有问题就评论,说不定会回复。