将英文字符串转换为数字

1. 需求

近期项目中遇到了一个需求,需要将英文字符串转换为阿拉伯数字。在stackoverflow上和csdn上都找了份代码,不过试验之后发现效果不太理想,所以打算自己写个工具类。参考这两份代码,自己也收获了一点灵感,然后就完成了这份这个工具类,且很好地满足了我的需求。

2. 代码实现

package com.frank.test.arithmetic;

import org.apache.commons.lang3.StringUtils;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
//import java.util.Objects;

/**
 * 将英文单词解析成数字。
* 核心思想是:将字符串按量级拆分,拆分得到的量级前面的数字值不能超过1000 * * @author Frank * @date 2016-12-26 */ public class EnglishWordsToNumber { /* * 小数点 */ private static final String POINT = "point"; /* * 数量级--百。用得比较多,所以单拿出来 */ private static final String HUNDRED_MANITUDE = "hundred"; /* * 数量级 */ private static final String[] MAGNITUDES = {"billion", "million", "thousand", "hundred"}; /* * 个位数 */ private static final String[] DIGITS = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine"}; /* * 十位数 */ private static final String[] TENS = {"twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"}; /* * 只能单独标识的数字[10, 19] */ private static final String[] TEENS = {"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"}; private static final Map map = new HashMap<>(); /* * 十个阿拉伯数字 */ private static final Map arabicNumerals = new HashMap<>(); static { map.put("zero", 0); // 添加1~9 for (int i = 0; i < DIGITS.length; i++) { map.put(DIGITS[i], i + 1); } // 添加10~19 for (int i = 0; i < TEENS.length; i++) { map.put(TEENS[i], i + 10); } // 添加TENS for (int i = 0; i < TENS.length; i++) { map.put(TENS[i], (i + 2) * 10); } for (int i = 0; i < TENS.length; i++) { for (int j = 0; j < DIGITS.length; j++) { map.put(TENS[i] + " " + DIGITS[j], (i + 2) * 10 + (j + 1)); map.put(TENS[i] + "-" + DIGITS[j], (i + 2) * 10 + (j + 1)); } } // "hundred", "thousand", "million", "billion" map.put("hundred", 100); map.put("thousand", 1000); map.put("million", 1000000); map.put("billion", 1000000000); arabicNumerals.put("zero", "0"); for (int i = 0; i < DIGITS.length; i++) { arabicNumerals.put(DIGITS[i], String.valueOf(i + 1)); } } /** * 将英文单词解析为一个数字 * * @param input * @return 如果无法进行合理化解析,就返回原值;否则返回解析后的值 */ public static String parse(String input) { if (StringUtils.isBlank(input)) { return input; } // 拆分为整数部分和小数部分 String[] parts = input.toLowerCase().replaceAll(" and ", " ").trim().split(POINT); // 如果整数部分只有zero if (parts[0].trim().equals("zero")) { // 判断小数部分的值 if (parts.length == 1) {// 没有小数部分 return "0"; } else if (parts.length == 2) { String decimal = parseAfterPoint(parts[1]); return decimal == null ? input : "0." + decimal; } else {// point出现了超过一次 return input; } } String decimal = null; if (parts.length > 2) {// point出现了超过一次 return input; } // 小数部分 if (parts.length == 2) { decimal = parseAfterPoint(parts[1]); if (decimal == null) {// 如果有小数部分,但是对小数部分解析后返回null,说明小数部分解析失败 return input; } } // 整数部分 String part0 = parts[0].trim(); BigInteger bi = BigInteger.ZERO; StringBuilder result = new StringBuilder(); for (int i = 0; i < MAGNITUDES.length && part0.length() > 0; i++) { Map ret = parseEveryMagnitude(part0, MAGNITUDES[i]); if (ret == null) { return input; } bi = bi.add((BigInteger) ret.get("number")); part0 = ret.get("part0").toString(); } // 加上整数部分 result.append(bi.toString()); // 加上小数部分 if (decimal != null) { result.append('.').append(decimal); } return result.toString(); } /** * 解析小数部分 * * @param str * @return 返回null表示小数部分解析失败,返回非null值表示解析成功 */ private static String parseAfterPoint(String str) { if (StringUtils.isBlank(str)) { return "0"; } StringBuilder builder = new StringBuilder(); String[] arr = StringUtils.split(str); for (String s : arr) { String num = arabicNumerals.get(s); if (num == null) {// 也就是说,小数点后面只能出现0~9的英文单词 return null; } builder.append(num); } return builder.toString(); } /** * 解析得到每个数量级前面的数字,然后和数量级相乘,得到该数量级的值 * * @param part0 * @param magnitude 数量级字符串 * @return 如果解析不成功,返回null,否则表示解析成功 */ private static Map parseEveryMagnitude(String part0, String magnitude) { Map ret = new HashMap<>(2); if (magnitude.equals(HUNDRED_MANITUDE)) {// 到了hundred这个数量级 int num = parseHundred(part0); if (num == -1) { return null; } ret.put("part0", ""); ret.put("number", BigInteger.valueOf(num)); return ret; } String[] arr = part0.split(magnitude); if (arr.length > 2) {// 字符串中包含多个数量级字符串 return null; } if (arr.length == 1) {// arr的长度是1,有两种情况:不包含数量级字符串,或者比如one billion if (part0.contains(magnitude)) { int num = parseHundred(arr[0].trim()); if (num == -1) { return null; } int magnitudeNum = map.get(magnitude).intValue(); ret.put("part0", ""); ret.put("number", BigInteger.valueOf(num).multiply(BigInteger.valueOf(magnitudeNum))); return ret; } else {// 不包含数量级字符串,就将part0原样返回 ret.put("part0", part0); ret.put("number", BigInteger.ZERO); return ret; } } if (arr.length == 2) {// 字符串中包含数量级字符串,那么只解析数量级字符串前面的数字内容 int num = parseHundred(arr[0].trim()); if (num == -1) { return null; } int magnitudeNum = map.get(magnitude).intValue(); ret.put("part0", arr[1].trim()); ret.put("number", BigInteger.valueOf(num).multiply(BigInteger.valueOf(magnitudeNum))); return ret; } return null; } /** * 解析每个数量级前面的数字及hundred数量级的数字。根据英语数字表示,该部分的值的范围是[1, 999]。
* * @param hundred * @return 如果解析失败,返回-1,否则返回解析后的值 */ private static int parseHundred(String hundred) { String[] arr = hundred.split(HUNDRED_MANITUDE); if (arr.length > 2) { return -1; } if (arr.length == 1) {// arr长度为1,有如下两种情况:one 或者 one hundred Integer num = map.get(arr[0].trim()); if (hundred.contains(HUNDRED_MANITUDE)) {// one hundred return (num != null && num.intValue() > 0 && num.intValue() < 10) ? num.intValue() * map.get(HUNDRED_MANITUDE) : -1; } else {// one return (num != null && num.intValue() > 0 && num.intValue() < 100) ? num.intValue() : -1; } } if (arr.length == 2) {// 包含hundred,那么hundred前面的值的范围就是[1, 9],hundred后面的值的范围就是[1, 99] Integer beforeHundred = map.get(arr[0].trim()); Integer afterHundred = map.get(arr[1].trim()); return (beforeHundred != null && beforeHundred.intValue() > 0 && beforeHundred.intValue() < 10 && afterHundred != null && afterHundred.intValue() > 0 && afterHundred.intValue() < 100) ? beforeHundred.intValue() * map.get(HUNDRED_MANITUDE).intValue() + afterHundred.intValue() : -1; } return -1; } // public static void main(String[] args) { // String[] words = {"one", "two", "five", "eleven", "twenty five", "fifty", "one hundred and one", // "one hundred and fifteen", "one hundred and ninety nine", "one hundred ten", // "one thousand one", // "one thousand and one", "one thousand and eleven", "one thousand and ninety nine", // "one thousand ninety nine", "one thousand one hundred and eleven", // "one thousand nine hundred and ninety nine", "ten thousand and one", // "ten thousand and twenty three", "ten thousand two hundred and twenty three", // "twelve thousand two hundred and twenty three", "one hundred thousand and one", // "two hundred two million fifty three thousand", "zero"}; // int[] expectedValue = {1, 2, 5, 11, 25, 50, 101, // 115, 199, 110, // 1001, // 1001, 1011, 1099, // 1099, 1111, // 1999, 10001, // 10023, 10223, // 12223, 100001, // 202053000, 0}; // for (int i = 0; i < words.length; i++) { // String word = words[i]; // String num = null; // try { // num = parse(word); // } catch (Exception e) { // e.printStackTrace(); // } // boolean result = Objects.equals(String.valueOf(expectedValue[i]), num); // if (result) { // System.out.println(word + "--->" + num + ", expected value is " + expectedValue[i] + ", result is " + result); // } else { // System.err.println(word + "--->" + num + ", expected value is " + expectedValue[i] + ", result is " + result); // } // } // } }


3. 简单分析

先来看一个数字:5,783,456 --> five million seven hundred and eighty-three thousand four hundred and fifty-six
将英文单词转换成数字,首先得分析一下英文的数字的读法。
第一,英文的数字,一般来说,是每隔三位有一个单词来表示该量级;
第二,两个量级之间,数字的数值范围是[1, 999];
第三,按数量级大小,将英文单词从左往右进行解析。

4. 不足之处

该代码将字符串 " and " 全替换成空字符了,所以对于给定英文字符串,无论有多少个 " and ",都可以进行解析,这从语义上来说不是太正确。

目前的代码只支持解析到billion,如果想要更大的数量级,那么需要做两步:

第一,将更大的数量级单词添加到MAGNITUDES数组的开头,各个数量级按照数量级从大到小排列;

第二,最好是修改静态属性 map 的 Value 的泛型为 BigInteger。



你可能感兴趣的:(日常编码)