前面刷过一个题,关于罗马字和阿拉伯数字转换,我之前的文章链接:
https://blog.csdn.net/sleepingboy888/article/details/89814335
最近在看《算法的乐趣》王晓华著,其中的“阿拉伯数字和中文数字”。这篇也是自己学习记录下。
阿拉伯数字转中文数字
具体的思路可以参考书中的段落,文章中只说重点的地方。中文数字中0的表述比较复杂,总结起来为一下三点:
- 以 10000 为小节,小节的结尾即使是 0 ,也不使用 “零”。例如:60000,6万
- 小节内(一个小节为 4 位数)两个非 0 数字之间要使用“零”。例如:504,五百零四
- 当小节的“千”位是 0 时,若本小节的前一小节无其他数字,则不用“零”,否则就要用“零”。50,五十;10050,一万零五十。
如果对这三条规则是否完善存有疑惑,可以在代码写完以后用测试用例来检查。
需要说明的名词:
- 节,中文数字一般以4位为一小段,也就是万分位,对应与英文中的千分位。
- 权,一节中每一位都有对于的值,例如:1234,1对于的权是1千。
- 节权,每一节都有自己的权,最低的4位可以理解为节权位空,例如:163481234,1亿6384万1234。特别说明,代码中节权只识别到万亿。
- 位权,每一位数字在一节中的对应的值,分别是:千,百,十,(个位为空)。
比较麻烦的可能是“零”的处理,书中归纳总结的挺好的,我个人稍微变通下。根据日常习惯,其实是阿拉伯数字中连续的零只读一个零。调试代码过程中,个人总结:
- 值为零的时候就不需要考虑权,节权和位权都不需要。
- 不出现多个“零”连续,有种情况需要特殊考虑,一节的最后几位都是0不需要“零”,例如:100000,一十万
- 返回字符串第一个字符和最后一个字符不能为“零”,当然,输入为0直接返回“零”。
public static string NumberToChinese(uint num)
{
string result = "";
if (num == 0)
{
return "零";
}
uint _num = num;
//string[] chn_str = new string[] { "零","壹","贰","叁","肆","伍","陆","柒","捌","玖"};
//string[] unit_value = new string[] { "", "拾", "佰", "仟" };
string[] chn_str = new string[] { "零","一", "二", "三", "四", "五", "六", "七", "八", "九" };
string[] section_value = new string[] { "","万","亿","万亿"};
string[] unit_value = new string[] { "", "十", "百", "千" };
uint section = _num % 10000;
for (int i = 0; _num != 0 && i < 4; i++)
{
if (section == 0)
{
//0不需要考虑节权值,不能出现连续的“零”
if (result.Length > 0 && result.Substring(0, 1) != "零")
{
result = "零" + result;
}
_num = _num / 10000;
section = _num % 10000;
continue;
}
result = section_value[i]+result;
uint unit = section % 10;
for (int j = 0; j<4 ; j++)
{
if (unit == 0)
{
//0不需要考虑位权值,不能出现联系的“零”,每节最后的0不需要
if (result.Length > 0 && result.Substring(0, 1) != "零" && result.Substring(0, 1) != section_value[i])
{
result = "零" + result;
}
}
else
{
result = chn_str[unit] + unit_value[j] + result;
}
section = section / 10;
unit = section % 10;
}
_num = _num / 10000;
section = _num % 10000;
}
if (result.Length > 0 && result.Substring(0, 1) == "零")
{
//清理最前面的"零"
result = result.Substring(1);
}
return result;
}
测试用例代码:
public Dictionary<uint, string> m_dic_test = new Dictionary<uint, string>
{
{ 0,"零" },
{ 1,"一" },
{ 2,"二" },
{ 3,"三" },
{ 4,"四" },
{ 5,"五" },
{ 6,"六" },
{ 7,"七" },
{ 8,"八" },
{ 9,"九" },
{ 10,"一十" },
{ 11,"一十一" },
{ 110,"一百一十" },
{ 111,"一百一十一" },
{ 100,"一百" },
{ 102,"一百零二" },
{ 1020,"一千零二十" },
{ 1001,"一千零一" },
{ 1015,"一千零一十五" },
{ 1000,"一千" },
{ 10000,"一万" },
{ 20010,"二万零一十" },
{ 20001,"二万零一" },
{ 100000,"一十万" },
{ 1000000,"一百万" },
{ 10000000,"一千万" },
{ 100000000,"一亿" },
{ 1000000000,"一十亿" },
{ 1000001000,"一十亿零一千" },
//{ 1000001000,"一十亿一千" },
{ 1000000100,"一十亿零一百" },
{ 200010,"二十万零一十" },
{ 2000105,"二百万零一百零五" },
{ 20001007,"二千万一千零七" },
{ 2000100190,"二十亿零一十万零一百九十" },
{ 1040010000,"一十亿四千零一万" },
{ 200012301,"二亿零一万二千三百零一" },
{ 2005010010,"二十亿零五百零一万零一十" },
{ 4009060200,"四十亿零九百零六万零二百" },
{ 4294967295,"四十二亿九千四百九十六万七千二百九十五" }
};
[TestMethod]
public void TestNumberToChinese()
{
foreach (var item in m_dic_test)
{
Assert.AreEqual(Solutions.NumberToChinese(item.Key), item.Value, "error");
}
}
按照我的标准和书中的不太一样,例如:
{ 1000001000,“一十亿一千” }
这个测试用例不能通过,代码解析为:一十亿零一千,我个人感觉这种读法也能接受。这代码不能到万亿,如果需要识别到万亿级,可以把 uint 类型替换掉。
中文数字转阿拉伯数字
这个的思路相对容易一些,主要是分段和分节,需要注意的点:
- “万亿”的特殊处理,这个是两个字的节权名。
- “十”,“百”,“千”,和数字成套出现,要么是数字加上这写数,或者数字单独出现(一个节中的最低位)
- 没有判断输入的字符串是否满足中文数字的表述规则,默认输入的字符串满足规则。
public static UInt64 ChineseToNumber(string chinese_str)
{
UInt64 result = 0;
Dictionary<string, UInt64> dic_section_number = new Dictionary<string, UInt64> {
{ "万",10000 },{ "亿",100000000 },{ "万亿",1000000000000 }
};
Dictionary<string, UInt64> dic_unit_number = new Dictionary<string, UInt64> {
{ "十",10 },{ "百",100 },{ "千",1000 }
};
Dictionary<string, UInt64> dic_chn_number = new Dictionary<string, UInt64> {
{ "零",0}, { "一",1}, { "二",2} ,{ "三",3},{ "四",4},{ "五",5},{ "六",6},{ "七",7},{ "八",8},{ "九",9}
};
if (chinese_str.Length == 0)
{
return 0;
}
UInt64 section_value = 0;
for (int i = 0; i < chinese_str.Length; i++)
{
string cur_str = chinese_str.Substring(i, 1);
if (dic_section_number.ContainsKey(cur_str))
{
//万亿的特殊处理
if (cur_str == "万" && i + 1 < chinese_str.Length && chinese_str.Substring(i + 1, 1) == "亿")
{
result += section_value * dic_section_number["万亿"];
i++;
}
else
{
result += section_value * dic_section_number[cur_str];
}
section_value = 0;
}
else
{
if(dic_chn_number.ContainsKey(cur_str))
{
if (i + 1 < chinese_str.Length && dic_unit_number.ContainsKey(chinese_str.Substring(i + 1, 1)))
{
//和数字一起成对出现的 十,百,千
section_value += dic_chn_number[cur_str] * dic_unit_number[chinese_str.Substring(i + 1, 1)];
i++;
}
else
{
section_value += dic_chn_number[cur_str];
}
}
}
}
result += section_value;
return result;
}
//测试函数
[TestMethod]
public void TestChineseToNumber()
{
foreach (var item in m_dic_test)
{
Assert.AreEqual(Solutions.ChineseToNumber(item.Value), item.Key, "error");
}
}
C# UnitTest 初使用
- 在工程中 Solution 上右键 ->Add-> New Project…
- 添加一个 (c#) Unit Test Project
- 在创建的 Unit Test 工程中把需要测试的工程添加到 Reference
- 在自动生成的测试代码中添加自己的测试函数,然后运行就可以了。记得 using namespace。