如何从写为单词的数字中读取值?
language-agnostic
nlp
6
0

众所周知,数字既可以用数字写成,也可以用名字来称呼。尽管有很多将123转换为123的示例,但我找不到如何将123转换为123的良好示例。

一些警告:

  1. 基数/标称或序数:“一个”和“第一”
  2. 常见的拼写错误:“四十” /“四十”
  3. 百/千:2100->“二十一”,还有“二十一百”
  4. 分隔符:“一百一十二点二十五”,也可以是“一百一十二点五十二”或“一百一十二点五十二”等
  5. 口语:“三十多岁”
  6. 分数:“三分之一”,“五分之二”
  7. 通用名称:“一打”,“半个”

而且可能还有更多未列出的警告。假设算法需要非常健壮,甚至可以理解拼写错误。

我应该阅读哪些领域/论文/研究/算法以学习如何编写所有这些内容?信息在哪里?

PS:我的最终解析器实际上应该理解3种不同的语言,英语,俄语和希伯来语。也许以后会添加更多的语言。希伯来语也有男性/女性数字,例如“一个男人”和“一个女人”具有不同的“一个”,即“ ehad”和“ ahat”。俄语也有其自身的复杂性。

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

http://www.google.com/search?q=小数点后加一千+一+一百+五+六+六+四+五+

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

参考资料:
Stack Overflow
收藏
评论
共 9 个回答
高赞 时间 活跃

序数不适用,因为它们不能以有意义的方式与语言中的其他数字(...至少是英语)结合在一起

例如一百一十一,十一秒等等...

但是,还有另一个英美警告:“ and”一词

一百一十一(英语)一百一十一(美国)

另外,用“ a”表示英语中的一个

一千=一千

...附带说明,Google的计算器在这方面做得非常出色。

光速的十三倍

乃至...

211加一打

... wtf?!? 比分加上一打罗马数字

收藏
评论

这不是一个容易的问题,而且我知道没有图书馆可以做到这一点。我可能会坐下来尝试写一些这样的东西。不过,我会在Prolog,Java或Haskell中进行操作。据我所知,有几个问题:

  • 记号化:有时,数字被写成1152,但是我看过112或112-52等等。人们将不得不对实际使用的表格进行调查。对于希伯来语来说,这可能特别棘手。
  • 拼写错误:这并不难。您的字词数量有限,可以用一点Levenshtein距离魔术来解决问题。
  • 就像您已经提到的,存在其他形式。这包括序数/基数,以及四十/四十和...
  • ...通用名称或常用短语和NE(命名实体)。您要从“三十年战争”中提取30个还是从第二次世界大战中提取2个?
  • 罗马数字呢?
  • 集体主义,例如“三十多岁”和“三欧元和弹片”,我不知道该如何对待。

如果您对此感兴趣,我可以在本周末试一下。我的想法可能是使用UIMA并对其进行标记化,然后进一步对标记化/消除歧义,最后进行翻译。可能还有更多问题,让我们看看是否可以提出一些更有趣的事情。

抱歉,这还不是真正的答案,只是您问题的延伸。如果我找到/写了一些东西,我会告诉你。

顺便说一句,如果您对数字的语义感兴趣,我刚刚找到了Friederike Moltmann的有趣论文 ,讨论了有关数字逻辑解释的一些问题。

收藏
评论

您应该记住,欧洲和美国的计数方式有所不同。

欧洲标准:

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)
收藏
评论

我有一段时间之前写的一些代码: text2num 。这可以满足您的要求,但它不能处理序数。我实际上并没有使用此代码,因此未经测试!

收藏
评论

使用Python pattern-en库:

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

这是Clojure中极其强大的解决方案。

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;
}
收藏
评论

好吧,对于这个问题的答案,我为时已晚,但是我正在研究一个似乎对我来说效果很好的测试场景。我使用了一个(简单但难看又大的)正则表达式来查找所有适合我的单词。表达式如下:

(?<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”类型,因为我没有添加它们,但是如您所见,这样做并不难。这解决了很多问题。例如,它解决了原始问题和其他答案中的以下项目:

  1. 基数/标称或序数:“一个”和“第一”
  2. 常见的拼写错误:“ tyty” /“ fourty”(注意,它并没有明确地解决这个问题,这是在将字符串传递给此解析器之前您要执行的操作。此解析器将本示例视为“四个”。 ..)
  3. 百/千:2100->“二十一”,还有“二十一百”
  4. 分隔符:“一百一十二点二十五”,也可以是“一百一十二点五十二”或“一百一十二点五十二”等
  5. 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,为其添加代码标记,并对其进行处理。

好吧,我希望这篇文章能对其他人有所帮助。如果有人愿意,我可以通过vbScript发布用于测试的伪代码,但这不是漂亮的代码,而不是生产代码。

如果可以的话,将使用的最终语言是什么? C ++还是类似脚本语言的东西?格雷格·休吉尔(Greg Hewgill)的资料将有助于理解所有这些因素。

让我知道我是否还有其他帮助。抱歉,我只会说英语/美国语,因此我无法为您提供其他语言的帮助。

收藏
评论

当我发现有一个非常简单的算法可以很好地处理英文,西班牙文和英文的常见数字形式时,我正在玩PEG解析器来做您想做的事情(以后可以将其作为一个单独的答案发布)。至少是德语。

例如,使用英语,您需要一个字典,该字典以明显的方式将单词映射到值:

"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

等等。我并不是说它是完美的,但是对于快速又肮脏的它来说效果很好。


在编辑时处理您的特定列表:

  1. 基数/标称或序数:“一个”和“第一”- 只需将它们放入字典中
  2. 英语/英语:“ fourty” /“ forty”- 同上
  3. 千/千:2100->“二十一”以及“二十一百”- 按原样工作
  4. 分隔符:“ 112”,也可以是“ 112”或“ 112 522”等( 仅将“下一个单词”定义为与定义的单词匹配的最长前缀,或者直到下一个)如果没有,则为非单词
  5. 口语主义:“三十年代”- 作品
  6. 片段:“三分之一”,“五分之二”- 嗯,还没有...
  7. 通用名称:“打”,“半”- 作品;您甚至可以做“六打”之类的事情

数字6是我唯一没有答案的答案,这是因为普通和分数之间的歧义(至少用英语来说),加之我最后一杯咖啡是在几个小时之前的。

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号