如何从数字中读取值作为单词?

众所周知,数字可以用数字来表示,也可以用名字来表示。 虽然有很多例子可以把123转换为123,但是我找不到如何将其转换成另一种方式的好例子。

一些注意事项:

  1. 基数/名义或序数:“一”和“第一”
  2. 常见的拼写错误:“四十”/“四十”
  3. 数百/数千:2100 – >“二百”,还有“二千一百”
  4. 分隔符:“十一五二”,还有“一百五十二”或“十一五二”,还有什么
  5. colloqialisms:“三十多岁”
  6. 片段:“三分之一”,“五分之二”
  7. 俗名:“一打”,“一半”

还有可能有更多的警告,可能还没有列出。 假设algorithm需要非常强大,甚至可以理解拼写错误。

我应该阅读哪些字段/论文/研究/algorithm来学习如何编写这些内容? 信息在哪里?

PS:我最后的parsing器实际上应该理解3种不同的语言,英语,俄语和希伯来语。 也许在稍后的阶段会增加更多的语言。 希伯来文也有男/女数字,像“一个男人”和“一个女人”有不同的“一”,“ehad”和“ahat”。 俄罗斯也有其自身的一些复杂性。

Google在这方面做得很好,例如:

http://www.google.com/search?q=two+thousand+and+one+hundred+plus+five+dozen+and+four+fifths+in+decimal

(反过来也是http://www.google.com/search?q=999999999999+in+english )

我正在玩一个PEGparsing器来做你想做的事情(可能以后作为一个单独的答案),当我注意到有一个非常简单的algorithm,用英语,西class牙语和德语,至less。

以英语为例,您需要一个能够以显而易见的方式将单词映射到值的字典:

"one" -> 1, "two" -> 2, ... "twenty" -> 20, "dozen" -> 12, "score" -> 20, ... "hundred" -> 100, "thousand" -> 1000, "million" -> 1000000 

…等等

该algorithm只是:

 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 

等等。 我并不是说这是完美的,但是对于一个快速和肮脏,它做得很好。


在编辑时解决您的特定列表:

  1. 主要/名义或序数:“一”和“第一” – 只是把它们放在字典中
  2. 英/英:“四十”/“四十” – 同上
  3. 数百/数千:2100 – >“二百”和“两千一百” – 按原样运作
  4. 分隔符:“1152”,也是“1152”或“1152”,而不是 – 将“下一个词”定义为与定义词相匹配的最长前缀,或者直到下一个词如果没有这样的话,那么一开始就是非词
  5. colloqialisms:“三十多岁” – 作品
  6. 片段:“三分之一”,“五分之二” – 呃,还没有…
  7. 俗名:“一打”,“一半” – 作品; 你甚至可以做“六打”

6号是唯一一个我没有准备好的答案,那是因为序号和分数之间的不明确(至less英文),加上我最后一杯咖啡是在很多小时以前。

这不是一个简单的问题,我知道没有图书馆去做。 我可能会坐下来试着写这样的东西。 不过,我会在Prolog,Java或者Haskell中做这个。 据我所知,有几个问题:

  • Tokenization:Tokenization:有时候,数字是写在1152,但是我看过1152,也就是1125。 人们将不得不对实际使用的forms进行调查。 希伯来文可能特别棘手。
  • 拼写错误:这并不难。 你有一个有限的话,有点Levenshtein距离的魔法应该做的伎俩。
  • 像你已经提到的其他forms存在。 这包括序数/基数,以及四十/四十和…
  • 常用名称或常用短语和NE(命名实体)。 你想从三十年战争中提取30还是从二战中提取2?
  • 罗马数字呢?
  • 如“三十”,“三欧”,我不知道该怎么处理。

如果你对这个感兴趣的话,我可以在这个周末给我一个机会。 我的想法可能是使用UIMA并用它进行标记,然后继续进行标记化/消歧并最终翻译。 可能还有更多的问题,让我们看看我能否提出一些更有趣的事情。

对不起,这还不是真正的答案,只是你的问题的扩展。 如果我find/写一些东西,我会告诉你。

顺便说一句,如果你对数字的语义感兴趣,我只是findFriederike Moltmann写的一篇有趣的论文 ,讨论关于数字逻辑解释的一些问题。

我有一些我刚才写的代码: text2num 。 这做一些你想要的,除了它不处理序数。 我实际上并没有使用这个代码,所以它基本上是未经testing的!

使用Python pattern-en库:

 >>> from pattern.en import number >>> number('two thousand fifty and a half') => 2050.5 

你应该记住,欧洲和美国的数字是不同的。

欧洲标准:

 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) 

序数不适用,因为它们不能以有意义的方式与其他数字相结合(至less英文)

如一百一十一秒等

然而,还有另一个英文/美国警告用'和'

一百一十(英)一百(美)

此外,使用“a”来表示英语中的一个

一千=一千

…在一个侧面说明谷歌的计算器做了这个惊人的工作。

十三万倍的光速

乃至…

二千一百多加一打

… WTF?!? 在罗马数字上加上十几个分数

这是一个非常强大的Clojure解决scheme。

AFAIK这是一个独特的实施方法。

 ;---------------------------------------------------------------------- ; numbers.clj ; written by: Mike Mattie codermattie@gmail.com ;---------------------------------------------------------------------- (ns operator.numbers (:use compojure.core) (:require [clojure.string :as string] )) (def number-word-table { "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 "fourty" 40 "fifty" 50 "sixty" 60 "seventy" 70 "eighty" 80 "ninety" 90 }) (def multiplier-word-table { "hundred" 100 "thousand" 1000 }) (defn sum-words-to-number [ words ] (apply + (map (fn [ word ] (number-word-table word)) words)) ) ; are you down with the sickness ? (defn words-to-number [ words ] (let [ n (count words) multipliers (filter (fn [x] (not (false? x))) (map-indexed (fn [ i word ] (if (contains? multiplier-word-table word) (vector i (multiplier-word-table word)) false)) words) ) x (ref 0) ] (loop [ indices (reverse (conj (reverse multipliers) (vector n 1))) left 0 combine + ] (let [ right (first indices) ] (dosync (alter x combine (* (if (> (- (first right) left) 0) (sum-words-to-number (subvec words left (first right))) 1) (second right)) )) (when (> (count (rest indices)) 0) (recur (rest indices) (inc (first right)) (if (= (inc (first right)) (first (second indices))) * +))) ) ) @x )) 

这里有些例子

 (operator.numbers/words-to-number ["six" "thousand" "five" "hundred" "twenty" "two"]) (operator.numbers/words-to-number ["fifty" "seven" "hundred"]) (operator.numbers/words-to-number ["hundred"]) 

我的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; } 

那么,对于这个问题的答案我太晚了,但是我正在做一个小testing,似乎对我来说效果很好。 我用一个(简单,但丑陋,大)的正则expression式来为我find所有的单词。 expression如下:

 (?<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,然后回读指定的匹配项。 这个问题在这个问题上列出的所有不同的例子都有效,减去了“一半”types,因为我没有添加它们,但正如你所看到的,这样做并不难。 这解决了很多问题。 例如,它在原始问题和其他答案中解决以下问题:

  1. 基数/名义或序数:“一”和“第一”
  2. 常见的拼写错误:“四十”/“四十”(请注意,它并不明确地解决这个问题,这是你将string传递给parsing器之前要做的事情,这个parsing器把这个例子看作“FOUR”。 ..)
  3. 数百/数千:2100 – >“二百”,还有“二千一百”
  4. 分隔符:“十一五二”,还有“一百五十二”或“十一五二”,还有什么
  5. colloqialisms:“三十多岁”(这也不是完全解决,什么是“东西”?那么,这个代码find这个数字只是“30”)。**

现在,我并没有在源代码中存储这个正则expression式的怪物,而是在运行时考虑构build这个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... 

这里容易的部分是我们只存储重要的话。 在第六种情况下,你会注意到它没有入口,因为这只是TH的正常数字。但是像TWELVE这样的需要不同的关注。

好的,现在我们有了构build我们的(丑陋的)RegEx的代码,现在我们只是在我们的数字string上执行它。

我会推荐的一件事是过滤,或者吃掉“AND”。 这不是必要的,只会导致其他问题。

所以,你要做的是设置一个函数,将“Magnitude”的命名匹配传递给一个函数,该函数查看所有可能的幅度值,并将当前结果乘以该值。 然后,创build一个函数,查看名为“匹配”的“值”,并根据在那里发现的值返回一个int(或任何您正在使用的)。

所有的VALUE匹配都被添加到您的结果中,而magnitutde匹配将结果乘以mag值。 因此,二十五万变成了“2”,然后是“2 * 100”,然后是“200 + 50”,然后是“250 * 1000”,结果是250000 …

只是为了好玩,我写了一个vbScript版本,它提供了所有的例子。 现在,它不支持命名匹配,所以我不得不努力得到正确的结果,但我明白了。 底线是,如果是“VALUE”匹配,则将其添加到累加器中。 如果它是一个数量级匹配,乘以你的累加器100,1000,1000000,1000000000等…这将为你提供一些相当惊人的结果,而你所要做的调整像“一半”的东西是添加它们到你的RegEx,放入一个代码标记为他们,并处理它们。

那么,我希望这个职位帮助某人在那里。 如果有人愿意,我可以通过vbScript伪代码发布,但是,这不是漂亮的代码,而是生产代码。

如果我可以…这将写在最后的语言是什么? C ++还是类似脚本语言的东西? Greg Hewgill的资料来源将帮助我们理解这一切是如何结合在一起的。

让我知道如果我可以有任何其他的帮助。 对不起,我只知道英语/美国人,所以我不能帮你用其他语言。

我把早期现代书籍(例如“第二版”,“Editio quarta”)的序数版本语句转换为整数,并需要支持英语序号1-100和一些罗曼语言1-10序号的支持。 以下是我在Python中想到的:

 def get_data_mapping(): data_mapping = { "1st": 1, "2nd": 2, "3rd": 3, "tenth": 10, "eleventh": 11, "twelfth": 12, "thirteenth": 13, "fourteenth": 14, "fifteenth": 15, "sixteenth": 16, "seventeenth": 17, "eighteenth": 18, "nineteenth": 19, "twentieth": 20, "new": 2, "newly": 2, "nova": 2, "nouvelle": 2, "altera": 2, "andere": 2, # latin "primus": 1, "secunda": 2, "tertia": 3, "quarta": 4, "quinta": 5, "sexta": 6, "septima": 7, "octava": 8, "nona": 9, "decima": 10, # italian "primo": 1, "secondo": 2, "terzo": 3, "quarto": 4, "quinto": 5, "sesto": 6, "settimo": 7, "ottavo": 8, "nono": 9, "decimo": 10, # french "premier": 1, "deuxième": 2, "troisième": 3, "quatrième": 4, "cinquième": 5, "sixième": 6, "septième": 7, "huitième": 8, "neuvième": 9, "dixième": 10, # spanish "primero": 1, "segundo": 2, "tercero": 3, "cuarto": 4, "quinto": 5, "sexto": 6, "septimo": 7, "octavo": 8, "noveno": 9, "decimo": 10 } # create 4th, 5th, ... 20th for i in xrange(16): data_mapping[str(4+i) + "th"] = 4+i # create 21st, 22nd, ... 99th for i in xrange(79): last_char = str(i)[-1] if last_char == "0": data_mapping[str(20+i) + "th"] = 20+i elif last_char == "1": data_mapping[str(20+i) + "st"] = 20+i elif last_char == "2": data_mapping[str(20+i) + "nd"] = 20+i elif last_char == "3": data_mapping[str(20+i) + "rd"] = 20+i else: data_mapping[str(20+i) + "th"] = 20+i ordinals = [ "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth" ] # create first, second ... ninth for c, i in enumerate(ordinals): data_mapping[i] = c+1 # create twenty-first, twenty-second ... ninty-ninth for ci, i in enumerate([ "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" ]): for cj, j in enumerate(ordinals): data_mapping[i + "-" + j] = 20 + (ci*10) + (cj+1) data_mapping[i.replace("y", "ieth")] = 20 + (ci*10) return data_mapping 

尝试

  1. 打开HTTP请求,以“ http://www.google.com/search?q=”+号码+“十进制+ +”。

  2. parsing你的号码的结果。

  3. caching数字/结果对,以随时间推移请求。

一个开始寻找的地方是gnu get_date lib ,它可以将任何英文文本dateparsing为时间戳。 虽然不是你正在寻找什么,他们解决类似的问题可以提供很多有用的线索。