Question

Comme nous le savons tous, les nombres peuvent être écrits en chiffres ou appelés par leur nom. Bien qu’il existe de nombreux exemples permettant de convertir le 123 en 123, je n’ai pas trouvé de bons exemples montrant comment le convertir en sens inverse.

Certaines mises en garde:

  1. cardinal / nominal ou ordinal: "un" et "premier"
  2. fautes d'orthographe courantes: "quarante" / "quarante"
  3. centaines / milliers: 2100 - > "vingt et cent" et aussi "deux mille cent"
  4. séparateurs: "onze cent cinquante deux", mais aussi "onze cent cinquante deux" ou "onze cent cinquante-deux" et tout le reste
  5. expressions familières: "trente-quelque chose"
  6. fractions: 'un tiers', 'deux cinquièmes'
  7. noms communs: "une douzaine", "demi"

Et il y a probablement plus de mises en garde possibles qui ne sont pas encore listées. Supposons que l’algorithme doit être très robuste et même comprendre les fautes d’orthographe.

Quels domaines / papiers / études / algorithmes dois-je lire pour apprendre à écrire tout cela? Où est l'information?

  

PS: Mon analyseur final devrait en réalité comprendre trois langues différentes: l’anglais, le russe et l’hébreu. Et peut-être qu’à un stade ultérieur, d’autres langues seront ajoutées. L'hébreu a aussi des nombres masculins / féminins, comme "un homme". et " une femme " avoir un "un" différent - "ehad" et "ahat". Le russe a aussi ses propres complexités.

Google fait un excellent travail dans ce domaine. Par exemple:

rel = "nofollow noreferrer"> http://www.google.com/search?q=two+thousand+and+and++++++++++++++++++++++++++++ + 3

(l'inverse est également possible http://www.google.com / search? q = 999999999999 + en + anglais )

Était-ce utile?

La solution

Je me suis amusé avec un analyseur PEG à faire ce que vous vouliez (et je pourrais l’afficher ultérieurement sous forme de réponse distincte) quand j’ai remarqué qu’il existe un algorithme très simple qui fait un travail remarquable avec les formes courantes de nombres en anglais, L'espagnol et l'allemand, au minimum.

Pour travailler avec l'anglais, par exemple, vous avez besoin d'un dictionnaire qui mappe les mots sur des valeurs de manière évidente:

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

... et ainsi de suite

L'algorithme est juste:

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

Par exemple, cela se déroule comme suit:

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

Et ainsi de suite. Je ne dis pas que c'est parfait, mais pour un rapide et sale, il fait très bien.

Adressant votre liste spécifique en édition:

  1. cardinal / nominal ou ordinal: "un" et " premier " - mettez-les simplement dans le dictionnaire
  2. anglais / britannique: "quarantaine" / "quarante" - idem
  3. des centaines / milliers:   2100 - > "vingt et cent" et aussi "deux mille cent". - fonctionne tel quel
  4. séparateurs: "onze cent cinquante deux", mais aussi "onze cent cinquante deux" ou "onze cent cinquante-deux" et tout le reste - simplement définir "& mot; mot suivant " être le préfixe le plus long qui correspond à un mot défini, ou jusqu'au non-mot suivant, si aucun ne correspond, pour commencer
  5. colloqialisms: "trente-quelque chose" - fonctionne
  6. fragments: "un tiers", "deux cinquièmes" - euh, pas encore ...
  7. noms communs: "une douzaine", "demi" - fonctionne; vous pouvez même faire des choses comme "une demi-douzaine"

Le numéro 6 est le seul auquel je n'ai pas de réponse toute prête, et c'est à cause de l'ambiguïté entre les ordinaux et les fractions (en anglais du moins) ajoutée au fait que ma dernière tasse de café était nombreuse il y a quelques heures.

Autres conseils

Ce n’est pas une question facile, et je ne connais aucune bibliothèque pour le faire. Je pourrais m'asseoir et essayer d'écrire quelque chose comme ça un jour. Je le ferais dans Prolog, Java ou Haskell, cependant. Autant que je sache, plusieurs problèmes se posent:

  • Tokenization: parfois, les nombres sont écrits onze cent cinquante-deux, mais j’en ai vu onze cent cinquante deux ou onze cent cinquante-deux et ainsi de suite. Il faudrait mener une enquête sur les formes actuellement utilisées. Cela pourrait être particulièrement difficile pour l'hébreu.
  • Les fautes d'orthographe: ce n'est pas si difficile. Vous avez un nombre de mots limité, et un peu de magie Levenshtein-distance devrait faire l'affaire.
  • Des formes alternatives, comme vous l'avez déjà mentionné, existent. Cela inclut les nombres ordinaux / cardinaux, ainsi que quarante / quarante et ...
  • ... noms communs ou phrases et NE utilisés couramment (entités nommées). Voudriez-vous en extraire 30 de la guerre de trente ans ou 2 de la Seconde Guerre mondiale?
  • Les chiffres romains aussi?
  • Colloqialismes, tels que "trente-quelque chose" et "trois euros et des éclats d'obus", que je ne saurais pas traiter.

Si cela vous intéresse, je pourrais essayer ce week-end. Mon idée est probablement d'utiliser UIMA et de créer des jetons avec, puis de poursuivre le processus de recherche / homonymie et enfin de traduire. Il pourrait y avoir plus de problèmes, voyons si je peux trouver des choses plus intéressantes.

Désolé, ce n'est pas encore une vraie réponse, mais une extension de votre question. Je vous ferai savoir si je trouve / écris quelque chose.

Au fait, si vous êtes intéressé par la sémantique des chiffres, je viens de trouver un article intéressant de Friederike Moltmann, discutant de questions relatives à l’interprétation logique des chiffres.

J'ai du code que j'ai écrit il y a longtemps: text2num . Cela fait une partie de ce que vous voulez, sauf que cela ne gère pas les nombres ordinaux. Je n'ai pas utilisé ce code pour quoi que ce soit, il n'a donc pratiquement pas été testé!

Utilisez la pattern-fr bibliothèque Python:

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

N'oubliez pas que l'Europe et l'Amérique comptent différemment.

norme européenne:

One Thousand
One Million
One Thousand Millions (British also use Milliard)
One Billion
One Thousand Billions
One Trillion
One Thousand Trillions

Here est une petite référence.

Voici un moyen simple de voir la différence:

(American counting Trillion) == (European counting Billion)

Les nombres ordinaux ne sont pas applicables car ils ne peuvent pas être associés de manière significative avec d'autres nombres dans la langue (... au moins en anglais)

par exemple. cent premier, onze seconde, etc ...

Cependant, il y a une autre mise en garde anglais / américaine avec le mot 'et'

c'est-à-dire

cent un (anglais) cent un (américain)

En outre, l'utilisation de 'a' pour désigner un en anglais

mille = mille

... Sur une note de côté, la calculatrice de Google fait un travail remarquable.

cent trois mille fois la vitesse de la lumière

Et même ...

deux mille cent plus une douzaine

... wtf?!? un score plus une douzaine en chiffres romains

Voici une solution extrêmement robuste dans Clojure.

D'après ce que j'en sais, c'est une approche de mise en œuvre unique.

;----------------------------------------------------------------------
; 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 ))

Voici quelques exemples

(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"])

Ma mise en œuvre LPC de certaines de vos exigences (anglais américain uniquement):

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

Eh bien, la réponse à cette question était trop tardive, mais je travaillais sur un petit scénario test qui semble avoir très bien fonctionné pour moi. J'ai utilisé une expression régulière (simple, mais laide et grande) pour localiser tous les mots pour moi. L’expression est la suivante:

(?<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)))

Présenté ici avec des sauts de ligne à des fins de formatage.

Quoi qu'il en soit, ma méthode consistait à exécuter ce RegEx avec une bibliothèque telle que PCRE, puis à relire les correspondances nommées. Et cela a fonctionné sur tous les différents exemples énumérés dans cette question, moins les types "One Half", comme je ne les ai pas ajoutés, mais comme vous pouvez le constater, ce ne serait pas difficile. Cela répond à beaucoup de problèmes. Par exemple, il aborde les éléments suivants dans la question d'origine et d'autres réponses:

  1. cardinal / nominal ou ordinal: "un" et "premier"
  2. fautes d'orthographe courantes: "quarante" / "quarantaine" (Notez qu’il ne s’agit pas explicitement de résoudre ce problème. C’est quelque chose que vous voudriez faire avant de transmettre la chaîne à cet analyseur. Cet analyseur considère cet exemple comme "FOUR" ...)
  3. centaines / milliers: 2100 - > "vingt et cent" et aussi "deux mille cent"
  4. séparateurs: "onze cent cinquante deux", mais aussi "onze cent cinquante deux" ou "onze cent cinquante-deux" et tout le reste
  5. colloqialisms: "trente-quelque chose" (Cela n’est pas non plus TOTALEMENT traité, comme ce qui EST "quelque chose"? Eh bien, ce code trouve ce nombre simplement comme "30"). **

Maintenant, plutôt que de stocker ce monstre d'une expression régulière dans votre source, je pensais créer ce RegEx au moment de l'exécution, en utilisant quelque chose comme ce qui suit:

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...

La partie facile ici est que nous ne stockons que les mots qui comptent. Dans le cas de SIXTH, vous remarquerez qu’il n’ya pas d’entrée, car c’est un numéro normal auquel TH est ajouté ... Mais ceux comme DOUZE méritent une attention différente.

Ok, nous avons maintenant le code pour construire notre (laid) RegEx, maintenant nous l’exécutons simplement sur nos chaînes numériques.

Une chose que je recommanderais, est de filtrer ou de manger le mot "ET". Ce n'est pas nécessaire et cela ne mène qu'à d'autres problèmes.

Par conséquent, vous souhaitez configurer une fonction qui transmet les correspondances nommées pour "Magnitude". dans une fonction qui examine toutes les valeurs de magnitude possibles et multiplie votre résultat actuel par cette valeur de magnitude. Ensuite, vous créez une fonction qui examine le "Valeur". correspond à des correspondances nommées et renvoie un int (ou ce que vous utilisez), en fonction de la valeur découverte à cet endroit.

Toutes les correspondances VALUE sont ajoutées au résultat, tandis que les correspondances magnétiques multiplient le résultat par la valeur maximale. Ainsi, deux cent cinquante mille deviennent "2", puis "2 * 100", puis "200 + 50", puis "250 * 1000", se terminant par 250000 ...

Juste pour le plaisir, j’ai écrit une version vbScript de cela et cela a très bien fonctionné avec tous les exemples fournis. Maintenant, il ne supporte pas les matchs nommés, alors j'ai dû travailler un peu plus fort pour obtenir le résultat correct, mais je l'ai eu. En fin de compte, s’il s’agit d’un " VALUE " correspond, ajoutez-y votre accumulateur. S'il s'agit d'une correspondance de magnitude, multipliez votre accumulateur par 100, 1 000, 1 000, 1 000 000, 1 000 000, etc. ajoutez-les à votre RegEx, mettez-y un marqueur de code et gérez-les.

Eh bien, j'espère que ce post aidera quelqu'un à l’extérieur. Si quelqu'un le souhaite, je peux poster du pseudo-code vbScript avec lequel j'ai déjà testé cela, mais ce n'est pas un joli code, et NON du code de production.

Si je peux. Quel est le langage final dans lequel il sera écrit? C ++, ou quelque chose comme un langage scripté? La source de Greg Hewgill aidera beaucoup à comprendre comment tout cela s’agit.

Faites-moi savoir si je peux vous être d'une autre aide. Désolé, je ne connais que l'anglais et l'américain, je ne peux donc pas vous aider avec les autres langues.

Je convertissais les énoncés d’édition ordinale des premiers livres modernes (par exemple, "2ème édition", "Editio quarta") en nombres entiers et nécessitais une prise en charge des ordinaux 1-100 en anglais et des ordinaux 1-10 dans quelques langues romanes. Voici ce que je propose en 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

Essayez

  1. Ouvrez une demande HTTP pour " http://www.google.com/ rechercher? q = " + nombre + "+ en + décimal".

  2. Analyser le résultat pour votre numéro.

  3. Mettez en cache les paires nombre / résultat pour traiter les demandes au fil du temps.

Vous pouvez commencer par regarder le gnu get_date lib , qui peut analyser à peu près n'importe quelle date textuelle anglaise dans un horodatage. Bien que ce ne soit pas exactement ce que vous recherchez, leur solution à un problème similaire pourrait fournir de nombreux indices utiles.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top