概述 String numberToVoice(int number) 的小测试. eg: 12345 整数转读音成 一万二千三百四十五, 这个题目美团要求是20分钟做完且能跑通, 不会做就不要去面试了.
解决第一阶段, 风风火火完成需求 我开始一看这个题目, 感觉很简单啊, 五位数, 加一个位描述, 对应 万 千 百 十 个位, 建立一个对应的 table就可以了
1 2 3 4 5 6 7 8 9 private static final Map<Integer, String> numberVoiceMap = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 10000 , "万" , 1000 , "千" , 100 , "百" , 10 , "十" , 1 , "" )) .build();
然后每个数, 0~9 同样建立一个table
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private static final Map<Integer, String> numberMap = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 0, "零" , 1, "一" , 2, "二" , 3, "三" , 4, "四" )) .putAll(ImmutableMap.of( 5, "五" , 6, "六" , 7, "七" , 8, "八" , 9, "九" )) .build();
接下来, 直接 循环 numberVoiceMap 拿到对应每位的数 和 位描述 即可. 然后啪啪啪就把代码写出来了, 加上测试案例也大概才华了16分钟左右, 而且测试类完美通过, 下面贴下代码
NumberUtils1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package com.luozi.api;import com.google.common.collect.ImmutableMap;import java.util.Map;import java.util.Objects;public class NumberUtils { private static final Map<Integer, String> numberVoiceMap = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 10000 , "万" , 1000 , "千" , 100 , "百" , 10 , "十" , 1 , "" )) .build(); private static final Map<Integer, String> numberMap = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 0 , "零" , 1 , "一" , 2 , "二" , 3 , "三" , 4 , "四" )) .putAll(ImmutableMap.of( 5 , "五" , 6 , "六" , 7 , "七" , 8 , "八" , 9 , "九" )) .build(); private static final int high = 100000 ; public static String numberToVoice (int num) { if (num >= high) { throw new IllegalStateException ("this method did't support number greater then " + high); } StringBuilder builder = new StringBuilder (); for (Map.Entry<Integer, String> entry : numberVoiceMap.entrySet()) { int count = num / entry.getKey(); if (count >= 1 ) { builder.append(numberMap.get(count)) .append(entry.getValue()); } num = num % entry.getKey(); } return builder.toString(); } }
NumberUtilsTest1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.luozi.api;import org.junit.Assert;import org.junit.Test;import static org.junit.Assert.*;public class NumberUtilsTest { @Test public void numberToVoice () throws Exception { Assert.assertEquals("一" , NumberUtils.numberToVoice(1 )); Assert.assertEquals("一十一" , NumberUtils.numberToVoice(11 )); Assert.assertEquals("一百一十一" , NumberUtils.numberToVoice(111 )); Assert.assertEquals("一千一百一十一" , NumberUtils.numberToVoice(1111 )); Assert.assertEquals("一万二千三百四十" , NumberUtils.numberToVoice(12340 )); Assert.assertEquals("一万二千三百四十五" , NumberUtils.numberToVoice(12345 )); } }
解决第二阶段, 加上 0 的处理 上面的代码貌似可以了, 可是突然我想到, 上面的数字咋没有出现 0啊, 万一出现12004这种数怎么办, 代码中没有做这种处理, 肯定有问题. 于是开始改代码, 中间几多艰辛在此不再细表. 反正是又花了二十多分钟, 才改完并测试通过, 好吧, 我承认我没有通过测试. 下面重新贴一下第二版代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 package com.luozi.api;import com.google.common.collect.ImmutableMap;import java.util.Map;import java.util.Objects;public class NumberUtils { private static final Map<Integer, String> numberVoiceMap = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 10000 , "万" , 1000 , "千" , 100 , "百" , 10 , "十" , 1 , "" )) .build(); private static final Map<Integer, String> numberMap = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 0 , "零" , 1 , "一" , 2 , "二" , 3 , "三" , 4 , "四" )) .putAll(ImmutableMap.of( 5 , "五" , 6 , "六" , 7 , "七" , 8 , "八" , 9 , "九" )) .build(); private static final int high = 100000 ; public static String numberToVoice (int num) { if (num >= high) { throw new IllegalStateException ("this method did't support number greater then " + high); } StringBuilder builder = new StringBuilder (); for (Map.Entry<Integer, String> entry : numberVoiceMap.entrySet()) { int count = num / entry.getKey(); if (count >= 1 ) { builder.append(numberMap.get(count)) .append(entry.getValue()); } else if (checkIsAppendZero(builder, entry)) { builder.append(numberMap.get(count)); } num = num % entry.getKey(); } CheckAndRemoveSpareZero(builder); return builder.toString(); } private static final int zero = 0 ; private static void CheckAndRemoveSpareZero (StringBuilder builder) { int length = builder.length(); while (length > 1 && getLastChar(builder).equals(numberMap.get(zero))) { length = builder.length(); if (Objects.equals(getLastChar(builder), numberMap.get(zero)) && length > 1 ) { builder.deleteCharAt(length - 1 ); } } } private static String getLastChar (StringBuilder builder) { int length = builder.length(); if (length >= 1 ){ return builder.charAt(length - 1 ) + "" ; } else { return "" ; } } private static boolean checkIsAppendZero (StringBuilder builder, Map.Entry<Integer, String> entry) { int length = builder.length(); return (length > 0 && !Objects.equals(getLastChar(builder), numberMap.get(zero))) || entry.getKey() == 1 ; } }
NumberUtilsTest1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.luozi.api;import org.junit.Assert;import org.junit.Test;import static org.junit.Assert.*;public class NumberUtilsTest { @Test public void numberToVoice () throws Exception { Assert.assertEquals("一" , NumberUtils.numberToVoice(1 )); Assert.assertEquals("一十一" , NumberUtils.numberToVoice(11 )); Assert.assertEquals("一百一十一" , NumberUtils.numberToVoice(111 )); Assert.assertEquals("一千一百一十一" , NumberUtils.numberToVoice(1111 )); Assert.assertEquals("一万二千三百四十" , NumberUtils.numberToVoice(12340 )); Assert.assertEquals("一万二千三百四十五" , NumberUtils.numberToVoice(12345 )); Assert.assertEquals("一万零四十五" , NumberUtils.numberToVoice(10045 )); Assert.assertEquals("零" , NumberUtils.numberToVoice(0 )); Assert.assertEquals("一十" , NumberUtils.numberToVoice(10 )); Assert.assertEquals("一百" , NumberUtils.numberToVoice(100 )); Assert.assertEquals("一百零一" , NumberUtils.numberToVoice(101 )); } }
解决第三阶段, 支持所有 int 整数 其实在一开始写这个需求的时候, 就默认忽视了一点, 以为这个数处理的最大位数是 5位数, 但实际上人家代码需求里面写的请清楚楚明明白白的是 整数转读音, java里面的整数是 4个字节的, 打开python算一下, 2**31 结果是 2147483648, 10位数,对应二十一亿四千七百八十三万三千六百四十八 ,上面的实现肯定处理不了这个数.
好吧,怎么改呢, 仔细看了看中文描述, 发现除了多了亿和万这两个描述, 每个四位的处理基本上都一样, 那就四位四位地读吧, 延续前面的思路, 定义了新的table
1 2 3 4 5 6 private static final Map<Integer, String> BIGNUMBER_VOICE_MAP = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 100000000, "亿" , 10000, "万" )) .build();
前面的方法重构成处理四位数的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private static final int ZERO_CHECK_NUMBER = 1000; private static StringBuilder processForFourBit(int num) { StringBuilder builder = new StringBuilder(); if (num < ZERO_CHECK_NUMBER) { // 是否需要在第一位添零 builder.append(NUMBER_MAP.get(zero)); } if (num >= HIGH) { throw new IllegalStateException("this method did't support number greater then " + HIGH); } for (Map.Entry<Integer, String> entry : NUMBER_VOICE_MAP.entrySet()) { // 拿到每一位的数 和 值 int count = num / entry.getKey(); if (count >= 1) { builder.append(NUMBER_MAP.get(count)) .append(entry.getValue()); } else if (checkIsAppendZero(builder, entry)) { builder.append(NUMBER_MAP.get(count)); } num = num % entry.getKey(); } CheckAndRemoveSpareLastZero(builder); return builder; }
然后在处理四位的 前面又套一层
1 2 3 4 5 6 7 8 9 10 11 12 13 StringBuilder builder = new StringBuilder(); for (Map.Entry<Integer, String> entry : BIGNUMBER_VOICE_MAP.entrySet()) { int count = num / entry.getKey(); num = num % entry.getKey(); if (count > 0) { builder.append(processForFourBit(count)) // 数字按照四位分组处理 .append(entry.getValue()); } } builder.append(processForFourBit(num));
最后加上测试案例, 执行完美通过, 可是这个过程又花了我二十多分钟, 好吧, 我承认我离美团越來越远了, 哈哈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.luozi.api; import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.*; /** * Created by luoziyihao on 6/19/17. */ public class NumberUtilsTest { @Test public void numberToVoice() throws Exception { Assert.assertEquals("一", NumberUtils.numberToVoice(1)); Assert.assertEquals("一十一", NumberUtils.numberToVoice(11)); Assert.assertEquals("一百一十一", NumberUtils.numberToVoice(111)); Assert.assertEquals("一千一百一十一", NumberUtils.numberToVoice(1111)); Assert.assertEquals("一万二千三百四十", NumberUtils.numberToVoice(12340)); Assert.assertEquals("一万二千三百四十五", NumberUtils.numberToVoice(12345)); Assert.assertEquals("一万零四十五", NumberUtils.numberToVoice(10045)); Assert.assertEquals("零", NumberUtils.numberToVoice(0)); Assert.assertEquals("一十", NumberUtils.numberToVoice(10)); Assert.assertEquals("一百", NumberUtils.numberToVoice(100)); Assert.assertEquals("一百零一", NumberUtils.numberToVoice(101)); Assert.assertEquals("一亿零三百万零三百零一", NumberUtils.numberToVoice(103000301)); Assert.assertEquals("二十一亿零三百万零三百零一", NumberUtils.numberToVoice(2103000301)); Assert.assertEquals("二十一亿零三百零一", NumberUtils.numberToVoice(2100000301)); Assert.assertEquals("二十一亿零三百", NumberUtils.numberToVoice(2100000300)); Assert.assertEquals("二十一亿", NumberUtils.numberToVoice(2100000000)); Assert.assertEquals("一百零一", NumberUtils.numberToVoice(101)); } }
下面贴一下实现类的完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 package com.luozi.api; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.Objects; /** * Created by luoziyihao on 6/19/17. */ public class NumberUtils { private static final Map<Integer, String> BIGNUMBER_VOICE_MAP = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 100000000, "亿" , 10000, "万" )) .build(); private static final Map<Integer, String> NUMBER_VOICE_MAP = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 1000, "千" , 100, "百" , 10, "十" , 1, "" )) .build(); private static final Map<Integer, String> NUMBER_MAP = ImmutableMap.<Integer, String>builder() .putAll(ImmutableMap.of( 0, "零" , 1, "一" , 2, "二" , 3, "三" , 4, "四" )) .putAll(ImmutableMap.of( 5, "五" , 6, "六" , 7, "七" , 8, "八" , 9, "九" )) .build(); private static final int HIGH = 10000; /** * 整数转读音 * * @param num * @return */ public static String numberToVoice(int num) { StringBuilder builder = new StringBuilder(); for (Map.Entry<Integer, String> entry : BIGNUMBER_VOICE_MAP.entrySet()) { int count = num / entry.getKey(); num = num % entry.getKey(); if (count > 0) { builder.append(processForFourBit(count)) // 数字按照四位分组处理 .append(entry.getValue()); } } builder.append(processForFourBit(num)); CheckAndRemoveSpareFirstZero(builder); CheckAndRemoveSpareLastZero(builder); return builder.toString(); } private static void CheckAndRemoveSpareFirstZero(StringBuilder builder) { int length = builder.length(); while (length > 1 && getFirstChar(builder).equals(NUMBER_MAP.get(zero))) { length = builder.length(); if (Objects.equals(getFirstChar(builder), NUMBER_MAP.get(zero)) && length > 1) { builder.deleteCharAt(zero); } } } private static final int ZERO_CHECK_NUMBER = 1000; private static StringBuilder processForFourBit(int num) { StringBuilder builder = new StringBuilder(); if (num < ZERO_CHECK_NUMBER) { // 是否需要在第一位添零 builder.append(NUMBER_MAP.get(zero)); } if (num >= HIGH) { throw new IllegalStateException("this method did't support number greater then " + HIGH); } for (Map.Entry<Integer, String> entry : NUMBER_VOICE_MAP.entrySet()) { // 拿到每一位的数 和 值 int count = num / entry.getKey(); if (count >= 1) { builder.append(NUMBER_MAP.get(count)) .append(entry.getValue()); } else if (checkIsAppendZero(builder, entry)) { builder.append(NUMBER_MAP.get(count)); } num = num % entry.getKey(); } CheckAndRemoveSpareLastZero(builder); return builder; } private static final int zero = 0; private static void CheckAndRemoveSpareLastZero(StringBuilder builder) { int length = builder.length(); while (length > 1 && getLastChar(builder).equals(NUMBER_MAP.get(zero))) { length = builder.length(); if (Objects.equals(getLastChar(builder), NUMBER_MAP.get(zero)) && length > 1) { builder.deleteCharAt(length - 1); } } } private static String getLastChar(StringBuilder builder) { int length = builder.length(); if (length >= 1) { return builder.charAt(length - 1) + ""; } else { return ""; } } private static String getFirstChar(StringBuilder builder) { int length = builder.length(); if (length >= 1) { return builder.charAt(0) + ""; } else { return ""; } } private static boolean checkIsAppendZero(StringBuilder builder, Map.Entry<Integer, String> entry) { int length = builder.length(); return (length >= 1 && !Objects.equals(getLastChar(builder), NUMBER_MAP.get(zero))) || entry.getKey() == 1; } }
解决第四阶段, 重构, 代码洁癖狂开始扫垃圾了 不得不承认, 上面某些代码是很丑陋的, 比如将 char 转成String 再比较, zero的描述从map里面取, 完全是多余的逻辑, 整个代码逻辑略显混乱, 方法和参数名都取的不好…
代码未优化完毕, 暂见https://github.com/luoziyihao/java-practice/blob/master/api/src/main/java/com/luozi/api/NumberUtils.java