如何从以单词形式写的数字中读取值?
众所周知,数字既可以用数字写成,也可以用名字来称呼。虽然有很多示例可以将123转换为123,但我找不到如何将123转换为123的良好示例。
一些警告:
- 基数/标称或者序数:"一个"和"第一"
- 常见的拼写错误:"四十" /"四十"
- 千/千:2100->"二十一",还有"二十一百"
- 分隔符:"一百一十二点二十五",也可以是"一百一十二点五十二"或者"一百一十二点五十二"等
- 口语:"三十多岁"
- 分数:"三分之一","五分之二"
- 通用名称:"一打","半个"
而且可能还有更多未列出的警告。
假设该算法必须非常健壮,甚至可以理解拼写错误。
我应该阅读哪些领域/论文/研究/算法以学习如何编写所有这些内容?
信息在哪里?
PS: My final parser should actually understand 3 different languages, English, Russian and Hebrew. And maybe at a later stage more languages will be added. Hebrew also has male/female numbers, like "one man" and "one woman" have a different "one" — "ehad" and "ahat". Russian also has some of its own complexities.
Google在这方面做得很好。例如:
http://www.google.com/search?q=十进制中的+1000
(相反,也可以http://www.google.com/search?q=999999999999+in+english)
解决方案
回答
我有一段时间之前写的一些代码:text2num。这可以满足某些要求,但它不能处理序数。我实际上并没有将这段代码用于任何事情,因此很大程度上未经测试!
回答
这不是一个容易的问题,而且我知道没有图书馆可以做到这一点。我可能会坐下来尝试写一些类似的东西。不过,我会在Prolog,Java或者Haskell中进行操作。据我所知,有几个问题:
- 标记化:有时,数字被写成1152,但是我看过112或者112-52等等。人们将不得不对实际使用的表格进行调查。对于希伯来语来说,这可能特别棘手。
- 拼写错误:这并不难。字词数量有限,可以用一点Levenshtein距离魔术来解决问题。
- 就像我们已经提到的那样,存在其他形式。这包括序数/基数,以及四十/四十和...
- ...通用名称或者常用短语和NE(命名实体)。我们要从"三十年战争"中提取30个,还是从第二次世界大战中提取2个?
- 罗马数字也是吗?
- 共产主义,例如"三十多岁"和"三欧元和弹片",我不知道该如何对待。
如果我们对此感兴趣,我可以在本周末试一下。我的想法可能是使用UIMA并对其进行标记化,然后继续进行进一步的标记化/消除歧义,最后进行翻译。可能还有更多问题,让我们看看是否可以提出一些更有趣的事情。
抱歉,这还不是真正的答案,只是我们问题的延伸。如果我找到/写了一些东西,我会告诉你。
顺便说一句,如果我们对数字的语义感兴趣,我刚刚找到了Friederike Moltmann的有趣论文,讨论了有关数字逻辑解释的一些问题。
回答
序数不适用,因为它们不能以有意义的方式与语言中的其他数字(...至少是英语)结合在一起
例如一百一十一,第二十一,依此类推...
但是,还有另一个英美警告:" and"一词
IE。
一百零一(英文)
一百一十一(美国人)
另外,用" a"表示英语中的一个
一千=一千
...附带说明,Google的计算器在这方面做得非常出色。
光速的十三十三倍
乃至...
211加一打
... wtf?!?比分加上一打罗马数字
回答
当我发现有一个非常简单的算法可以很好地处理英文,西班牙文和至少是德语。
例如,在使用英语的情况下,我们需要一个字典来以明显的方式将单词映射到值:
"one" -> 1, "two" -> 2, ... "twenty" -> 20, "dozen" -> 12, "score" -> 20, ... "hundred" -> 100, "thousand" -> 1000, "million" -> 1000000
...等等
该算法只是:
total = 0 prior = null for each word w v <- value(w) or next if no value defined prior <- case when prior is null: v when prior > v: prior+v else prior*v else if w in {thousand,million,billion,trillion...} total <- total + prior prior <- null total = total + prior unless prior is null
例如,它的进度如下:
total prior v unconsumed string 0 _ four score and seven 4 score and seven 0 4 20 and seven 0 80 _ seven 0 80 7 0 87 87 total prior v unconsumed string 0 _ two million four hundred twelve thousand eight hundred seven 2 million four hundred twelve thousand eight hundred seven 0 2 1000000 four hundred twelve thousand eight hundred seven 2000000 _ 4 hundred twelve thousand eight hundred seven 2000000 4 100 twelve thousand eight hundred seven 2000000 400 12 thousand eight hundred seven 2000000 412 1000 eight hundred seven 2000000 412000 1000 eight hundred seven 2412000 _ 8 hundred seven 2412000 8 100 seven 2412000 800 7 2412000 807 2412807
等等。我并不是说它是完美的,但是对于快速又肮脏的它来说效果很好。
在编辑时处理特定列表:
- 基数/标称或者序数:"一个"和"第一"-只需将它们放入字典中
- 英语/英语:" fourty" /" forty"-同上
- 千/千:2100->"二十一"以及"二十一百"-按原样工作
- 分隔符:" 112",也可以是" 112"或者" 112 522"等(仅将"下一个单词"定义为与定义的单词匹配的最长前缀,或者直到下一个)首先,如果没有单词,则为非单词
- 口语主义:"三十年代"-作品
- 片段:"三分之一","五分之二"-嗯,还没有...
- 通用名称:"打","半"-作品;我们甚至可以做"六打"之类的事情
数字6是我唯一没有答案的答案,这是因为普通和分数之间的含糊不清(至少用英语来说),加之我最后一杯咖啡是很多小时前的事实。
回答
我对某些要求的LPC实施(仅限美式英语):
internal mapping inordinal = ([]); internal mapping number = ([]); #define Numbers ([\ "zero" : 0, \ "one" : 1, \ "two" : 2, \ "three" : 3, \ "four" : 4, \ "five" : 5, \ "six" : 6, \ "seven" : 7, \ "eight" : 8, \ "nine" : 9, \ "ten" : 10, \ "eleven" : 11, \ "twelve" : 12, \ "thirteen" : 13, \ "fourteen" : 14, \ "fifteen" : 15, \ "sixteen" : 16, \ "seventeen" : 17, \ "eighteen" : 18, \ "nineteen" : 19, \ "twenty" : 20, \ "thirty" : 30, \ "forty" : 40, \ "fifty" : 50, \ "sixty" : 60, \ "seventy" : 70, \ "eighty" : 80, \ "ninety" : 90, \ "hundred" : 100, \ "thousand" : 1000, \ "million" : 1000000, \ "billion" : 1000000000, \ ]) #define Ordinals ([\ "zeroth" : 0, \ "first" : 1, \ "second" : 2, \ "third" : 3, \ "fourth" : 4, \ "fifth" : 5, \ "sixth" : 6, \ "seventh" : 7, \ "eighth" : 8, \ "ninth" : 9, \ "tenth" : 10, \ "eleventh" : 11, \ "twelfth" : 12, \ "thirteenth" : 13, \ "fourteenth" : 14, \ "fifteenth" : 15, \ "sixteenth" : 16, \ "seventeenth" : 17, \ "eighteenth" : 18, \ "nineteenth" : 19, \ "twentieth" : 20, \ "thirtieth" : 30, \ "fortieth" : 40, \ "fiftieth" : 50, \ "sixtieth" : 60, \ "seventieth" : 70, \ "eightieth" : 80, \ "ninetieth" : 90, \ "hundredth" : 100, \ "thousandth" : 1000, \ "millionth" : 1000000, \ "billionth" : 1000000000, \ ]) varargs int denumerical(string num, status ordinal) { if(ordinal) { if(member(inordinal, num)) return inordinal[num]; } else { if(member(number, num)) return number[num]; } int sign = 1; int total = 0; int sub = 0; int value; string array parts = regexplode(num, " |-"); if(sizeof(parts) >= 2 && parts[0] == "" && parts[1] == "-") sign = -1; for(int ix = 0, int iix = sizeof(parts); ix < iix; ix++) { string part = parts[ix]; switch(part) { case "negative" : case "minus" : sign = -1; continue; case "" : continue; } if(ordinal && ix == iix - 1) { if(part[0] >= '0' && part[0] <= '9' && ends_with(part, "th")) value = to_int(part[..<3]); else if(member(Ordinals, part)) value = Ordinals[part]; else continue; } else { if(part[0] >= '0' && part[0] <= '9') value = to_int(part); else if(member(Numbers, part)) value = Numbers[part]; else continue; } if(value < 0) { sign = -1; value = - value; } if(value < 10) { if(sub >= 1000) { total += sub; sub = value; } else { sub += value; } } else if(value < 100) { if(sub < 10) { sub = 100 * sub + value; } else if(sub >= 1000) { total += sub; sub = value; } else { sub *= value; } } else if(value < sub) { total += sub; sub = value; } else if(sub == 0) { sub = value; } else { sub *= value; } } total += sub; return sign * total; }
回答
开始查找的地方之一是gnu get_date lib,它可以将几乎任何英文文本日期解析为时间戳。尽管不是我们要找的东西,但他们为类似问题提供的解决方案可能会提供很多有用的线索。
回答
我们应该记住,欧洲和美国的计数方式有所不同。
欧洲标准:
One Thousand One Million One Thousand Millions (British also use Milliard) One Billion One Thousand Billions One Trillion One Thousand Trillions
这是一个小参考。
观察差异的简单方法如下:
(American counting Trillion) == (European counting Billion)
回答
尝试
- 打开对" http://www.google.com/search?q=" +数字+" + in +十进制"的HTTP请求。
- 解析结果为数字。
- 缓存数字/结果对,以随着时间的流逝来请求请求。
回答
好吧,我对于这个问题的答案还为时已晚,但是我正在研究一个似乎对我来说非常有效的小测试方案。我使用了一个(简单但难看又大的)正则表达式来查找所有适合我的单词。表达式如下:
(?<Value>(?:zero)|(?:one|first)|(?:two|second)|(?:three|third)|(?:four|fourth)| (?:five|fifth)|(?:six|sixth)|(?:seven|seventh)|(?:eight|eighth)|(?:nine|ninth)| (?:ten|tenth)|(?:eleven|eleventh)|(?:twelve|twelfth)|(?:thirteen|thirteenth)| (?:fourteen|fourteenth)|(?:fifteen|fifteenth)|(?:sixteen|sixteenth)| (?:seventeen|seventeenth)|(?:eighteen|eighteenth)|(?:nineteen|nineteenth)| (?:twenty|twentieth)|(?:thirty|thirtieth)|(?:forty|fortieth)|(?:fifty|fiftieth)| (?:sixty|sixtieth)|(?:seventy|seventieth)|(?:eighty|eightieth)|(?:ninety|ninetieth)| (?<Magnitude>(?:hundred|hundredth)|(?:thousand|thousandth)|(?:million|millionth)| (?:billion|billionth)))
此处以换行符显示,用于格式化。
无论如何,我的方法是使用PCRE之类的库执行此RegEx,然后读回命名的匹配项。它可以处理此问题中列出的所有不同示例,减去" One Half"类型,因为我没有添加它们,但是如我们所见,这样做并不难。这解决了很多问题。例如,它解决了原始问题和其他答案中的以下项目:
- 基数/标称或者序数:"一个"和"第一"
- 常见的拼写错误:"四十" /"四十"(请注意,它并没有明确解决这个问题,这是我们在将字符串传递给此解析器之前想要做的事情。此解析器将本示例视为"四"。 ..)
- 千/千:2100->"二十一",还有"二十一百"
- 分隔符:"一百一十二点二十五",也可以是"一百一十二点五十二"或者"十一十二点五十二"等
- colloqialisms:"三十多岁"(这也不是总能解决的,因为"东西"是什么?嗯,这段代码发现这个数字就是" 30")。
现在,我没有考虑将正则表达式的怪物存储在源代码中,而是考虑在运行时使用以下内容构建此RegEx:
char *ones[] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"}; char *tens[] = {"", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"}; char *ordinalones[] = { "", "first", "second", "third", "fourth", "fifth", "", "", "", "", "", "", "twelfth" }; char *ordinaltens[] = { "", "", "twentieth", "thirtieth", "fortieth", "fiftieth", "sixtieth", "seventieth", "eightieth", "ninetieth" }; and so on...
这里最简单的部分是我们只存储重要的单词。在SIXTH的情况下,我们会注意到没有条目,因为这只是加TH的正常数字。但是像TWELVE这样的条目需要不同的注意。
好的,现在我们有了构建(丑陋的)RegEx的代码,现在只需对数字字符串执行它即可。
我建议的一件事是过滤或者吃掉" AND"一词。这是没有必要的,只会导致其他问题。
因此,我们要做的是设置一个函数,该函数将命名为" Magnitude"的匹配项传递到一个函数,该函数查看所有可能的幅度值,并将当前结果乘以该幅度值。然后,创建一个函数,该函数查看名为match的" Value",并根据在此发现的值返回一个int(或者我们正在使用的任何值)。
所有VALUE个匹配项都添加到结果中,而magnitutde匹配项将结果乘以mag值。因此,二十万变成" 2",然后是" 2 * 100",然后是" 200 + 50",然后是" 250 * 1000",最后变成250000 ...
只是为了好玩,我写了一个vbScript版本,它与提供的所有示例都很好用。现在,它不支持命名匹配,因此我不得不更加努力地获得正确的结果,但是我明白了。底线是,如果它是" VALUE"匹配项,则将其添加为累加器。如果是大小匹配,则将累加器乘以100、1000、1000000、1000000000等。这将为我们提供一些非常惊人的结果,而我们要做的所有调整(如"一半")的操作都将它们相加到RegEx,为它们放置一个代码标记,并对其进行处理。
好吧,我希望这篇文章能对SOMEONE有所帮助。如果有人愿意,我可以通过vbScript发布用于测试的伪代码,但是,它不是漂亮的代码,而不是生产代码。
如果可以的话,将使用的最终语言是什么? C ++还是类似脚本语言的东西?格雷格·休吉尔(Greg Hewgill)的资料将有助于理解所有这些因素。
让我知道我是否还有其他帮助。抱歉,我只会说英语/美国语,因此我无法为我们提供其他语言的帮助。