51

众所周知,数字可以写成数字,也可以用它们的名字来称呼。虽然可以找到很多将 123 转换为 123 的示例,但我找不到如何将其反过来转换的好示例。

一些警告:

  1. 基数/名义或序数:“一”和“第一”
  2. 常见拼写错误:“四十”/“四十”
  3. 百/千:2100 -> “2100”和“2010”
  4. 分隔符:“十一百五十二”,还有“十一百五十二”或“十一百五十二”等等
  5. 俗语:“三十多岁”
  6. 分数:'三分之一','五分之二'
  7. 常用名称:“一打”、“半”

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

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

PS:我的最终解析器实际上应该理解 3 种不同的语言,英语、俄语和希伯来语。也许在稍后阶段会添加更多语言。希伯来语也有男性/女性数字,例如“one man”和“one woman”有不同的“one”——“ehad”和“ahat”。俄语也有其自身的一些复杂性。

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

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

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

4

12 回答 12

46

当我注意到有一个非常简单的算法可以很好地处理英语、西班牙语和德语,至少。

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

"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 -> “2100”和“2010”——按原样工作
  4. 分隔符:“十一五十二”,还有“十一百五十二”或“十一百五十二”等等——只需将“下一个单词”定义为与定义的单词匹配的最长前缀,或者直到下一个非词如果没有,首先
  5. 口语:“三十多岁”——作品
  6. 片段:“三分之一”,“五分之二”——呃,还没有……
  7. 常用名称:“一打”、“半”——有效;你甚至可以做“半打”之类的事情

6 号是唯一一个我没有现成答案的,这是因为序数和分数之间的歧义(至少在英语中)加上我最后一杯咖啡是几个小时前的事实。

于 2009-03-17T05:28:46.030 回答
12

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

  • 标记化:有时,数字写成 1152,但我见过 1152 或 1152 等等。人们必须对实际使用的表格进行调查。这对希伯来语来说可能特别棘手。
  • 拼写错误:这并不难。你的单词数量有限,一点 Levenshtein 距离魔法应该可以解决问题。
  • 就像您已经提到的那样,存在替代形式。这包括序数/基数,以及四十/四十和...
  • ... 常用名称或常用短语和 NE(命名实体)。你想从三十年战争中提取 30 个还是从二战中提取 2 个?
  • 罗马数字也行?
  • “三十多岁”、“三欧弹片”之类的口语,不知如何对待。

如果你对此感兴趣,我可以在这个周末试一试。我的想法可能是使用 UIMA 并用它进行标记,然后继续进一步标记/消除歧义并最终翻译。可能还有更多的问题,让我们看看我能不能想出一些更有趣的东西。

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

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

于 2008-09-18T00:26:19.363 回答
11

我有一些我不久前写的代码:text2num。这可以满足您的一些需求,但它不处理序数。我实际上并没有将这段代码用于任何事情,所以它在很大程度上未经测试!

于 2008-09-16T07:52:42.817 回答
7

使用 Python pattern-en库:

>>> from pattern.en import number
>>> number('two thousand fifty and a half') => 2050.5
于 2011-08-03T11:39:30.363 回答
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)
于 2009-03-17T14:06:00.437 回答
4

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

比如一百零一秒,十一秒等等……

但是,还有另一个关于“和”这个词的英语/美国警告

IE

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

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

一千=一千

...在旁注中,Google 的计算器在这方面做得非常出色。

13000倍光速

乃至...

两千一百加一打

……呜呜呜?!?一个分数加上十几个罗马数字

于 2009-03-14T00:56:06.637 回答
3

这是 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"])
于 2011-11-13T14:16:12.323 回答
2

我的 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;
}
于 2009-03-17T05:43:39.130 回答
2

好吧,我在回答这个问题时为时已晚,但我正在研究一个似乎对我来说效果很好的小测试场景。我使用了一个(简单但丑陋且大的)正则表达式来为我定位所有单词。表达式如下:

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

  1. 基数/名义或序数:“一”和“第一”
  2. 常见的拼写错误:“forty”/“fourty”(请注意,它没有明确解决这个问题,这将是您在将字符串传递给此解析器之前想要做的事情。此解析器将此示例视为“FOUR”。 ..)
  3. 百/千:2100 -> “2100”和“2010”
  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”这个词。这没有必要,只会导致其他问题。

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

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

只是为了好玩,我为此编写了一个 vbScript 版本,它与提供的所有示例配合得很好。现在,它不支持命名匹配,所以我不得不更加努力地获得正确的结果,但我明白了。底线是,如果它是“价值”匹配,请将其添加到您的累加器中。如果是幅度匹配,请将您的累加器乘以 100、1000、1000000、1000000000 等...这将为您提供一些非常惊人的结果,而您所要做的调整“一半”之类的事情就是将它们相加到你的正则表达式,为它们输入一个代码标记,然后处理它们。

好吧,我希望这篇文章能帮助一些人。如果有人愿意,我可以通过我用来测试它的 vbScript 伪代码发布,但是,它不是漂亮的代码,也不是生产代码。

如果可以的话.. 这将用什么语言编写?C++,还是类似脚本语言的东西?Greg Hewgill 的消息来源将大大有助于理解所有这些是如何结合在一起的。

让我知道我是否可以提供任何其他帮助。抱歉,我只懂英语/美语,所以无法帮助您了解其他语言。

于 2009-03-20T23:33:46.087 回答
0

我正在将早期现代书籍(例如“第 2 版”、“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
于 2016-12-29T21:29:39.090 回答
-2

一个开始寻找的地方是gnu get_date lib,它可以将几乎任何英文文本日期解析为时间戳。虽然不完全是您正在寻找的东西,但他们对类似问题的解决方案可以提供很多有用的线索。

于 2009-03-17T14:00:02.540 回答
-2

尝试

  1. 打开一个 HTTP 请求到“ http://www.google.com/search?q= ” + number + “+in+decimal”。

  2. 解析您的号码的结果。

  3. 缓存数字/结果对以随着时间的推移对请求进行培训。

于 2009-03-19T19:42:52.483 回答