Pregunta

Como todos sabemos, los números se pueden escribir en números o llamarse por sus nombres.Si bien se pueden encontrar muchos ejemplos que convierten 123 en ciento veintitrés, no pude encontrar buenos ejemplos de cómo convertirlo al revés.

Algunas de las advertencias:

  1. cardinal/nominal u ordinal:"uno" y "primero"
  2. errores ortográficos comunes:"cuarenta"/"cuarenta"
  3. centenas/miles:2100 -> "veinte cien" y también "dos mil cien"
  4. separadores:"oncecientos cincuenta y dos", pero también "oncecientos cincuenta y dos" o "oncecientos cincuenta y dos" y otras cosas
  5. coloquialismos:"treinta y tantos"
  6. fracciones:'un tercio', 'dos ​​quintos'
  7. nombres comunes:'una docena', 'media'

Y probablemente haya más advertencias posibles que aún no figuran en la lista.Supongamos que el algoritmo debe ser muy sólido e incluso comprender los errores ortográficos.

¿Qué campos/artículos/estudios/algoritmos debo leer para aprender a escribir todo esto?¿Dónde está la información?

PD:Mi analizador final debería comprender 3 idiomas diferentes: inglés, ruso y hebreo.Y tal vez en una etapa posterior se agreguen más idiomas.El hebreo también tiene números masculino/femenino, como "un hombre" y "una mujer" tienen un "uno" diferente: "ehad" y "ahat".El ruso también tiene algunas de sus propias complejidades.

Google hace un gran trabajo en esto.Por ejemplo:

http://www.google.com/search?q=dos+mil+y+un+cien+más+cinco+docenas+y+cuatro+quintos+en+decimal

(lo contrario también es posible http://www.google.com/search?q=999999999999+en+inglés)

¿Fue útil?

Solución

Estaba jugando con un analizador PEG para hacer lo que querías (y puede que lo publique como una respuesta separada más adelante) cuando noté que hay un algoritmo muy simple que hace un trabajo notablemente bueno con formas comunes de números en inglés, español y Alemán, como mínimo.

Al trabajar con inglés, por ejemplo, necesita un diccionario que asigne palabras a valores de la manera obvia:

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

...Etcétera

El algoritmo es simplemente:

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

Por ejemplo, esto progresa de la siguiente manera:

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

Etcétera.No digo que sea perfecto, pero para un uso rápido y sucio va bastante bien.


Abordando su lista específica en edición:

  1. cardinal/nominal u ordinal:"uno" y "primero" -- solo ponlos en el diccionario
  2. inglés/británico:"cuarenta"/"cuarenta" -- ídem
  3. centenas/miles:2100 -> "veinte cien" y también "dos mil cien" -- funciona como es
  4. separadores:"oncecientos cincuenta y dos", pero también "oncecientos cincuenta y dos" o "oncecientos cincuenta y dos" y todo eso... simplemente defina "siguiente palabra" para que sea el prefijo más largo que coincida con una palabra definida, o hasta la siguiente no palabra si ninguna lo hace, para empezar
  5. coloquialismos:"treinta y tantos" -- obras
  6. fragmentos:'un tercio', 'dos ​​quintos' -- eh, todavía no...
  7. nombres comunes:'una docena', 'media' -- obras;incluso puedes hacer cosas como "media docena"

El número 6 es el único para el que no tengo una respuesta lista, y eso se debe a la ambigüedad entre ordinales y fracciones (al menos en inglés) sumada al hecho de que mi última taza de café fue muchos horas atras.

Otros consejos

No es una cuestión fácil y no conozco ninguna biblioteca que pueda hacerlo.Podría sentarme e intentar escribir algo como esto en algún momento.Sin embargo, lo haría en Prolog, Java o Haskell.Por lo que puedo ver, hay varios problemas:

  • Tokenización:a veces, los números se escriben mil ciento cincuenta y dos, pero he visto oncecientos cincuenta y dos o mil ciento cincuenta y dos y esas cosas.Habría que realizar una encuesta sobre qué formularios se utilizan realmente.Esto podría resultar especialmente complicado en el caso del hebreo.
  • Faltas de ortografía:eso no es tan difícil.Tienes una cantidad limitada de palabras y un poco de magia de distancia Levenshtein debería ser suficiente.
  • Existen formas alternativas, como ya mencionó.Esto incluye números ordinales/cardinales, así como cuarenta/cuarenta y...
  • ...nombres comunes o frases de uso común y NE (entidades nombradas).¿Le gustaría extraer 30 de la Guerra de los Treinta Años o 2 de la Segunda Guerra Mundial?
  • ¿Números romanos también?
  • Coloquialismos como "treinta y tantos" y "tres euros y metralla", que no sabría cómo tratar.

Si estás interesado en esto, podría intentarlo este fin de semana.Mi idea probablemente sea usar UIMA y tokenizar con él, luego continuar tokenizando/desambiguando y finalmente traduciendo.Puede que haya más problemas, veamos si se me ocurren cosas más interesantes.

Lo sentimos, esta aún no es una respuesta real, solo una extensión de tu pregunta.Te avisaré si encuentro/escribo algo.

Por cierto, si estás interesado en la semántica de los números, acabo de encontrar un papel interesante por Friederike Moltmann, discutiendo algunas cuestiones relacionadas con la interpretación lógica de los números.

Tengo un código que escribí hace un tiempo: texto2num.Esto hace parte de lo que desea, excepto que no maneja números ordinales.En realidad, no he usado este código para nada, ¡así que no ha sido probado en gran medida!

Usa el pitón patrón-es biblioteca:

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

Debes tener en cuenta que Europa y América cuentan de forma diferente.

Estándar europeo:

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

Aquí Hay una pequeña referencia al respecto.


Una forma sencilla de ver la diferencia es la siguiente:

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

Los números ordinales no son aplicables porque no se pueden unir de manera significativa con otros números en el idioma (... al menos en inglés)

p.ej.ciento primero, once segundos, etc...

Sin embargo, hay otra advertencia inglesa/estadounidense con la palabra "y"

es decir.

Ciento uno (inglés) cien uno (estadounidense)

Además, el uso de 'a' para significar uno en inglés

mil = mil

... Como nota al margen, la calculadora de Google hace un trabajo increíble al respecto.

ciento tres mil veces la velocidad de la luz

E incluso...

dos mil cien más una docena

...¡¿Qué?!? una partitura más una docena en números romanos

Aquí hay una solución extremadamente sólida en Clojure.

AFAIK es un enfoque de implementación único.

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

Aquí hay unos ejemplos

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

Mi implementación LPC de algunos de sus requisitos (solo inglés americano):

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

Bueno, llegué demasiado tarde a la respuesta a esta pregunta, pero estaba trabajando en un pequeño escenario de prueba que parece haber funcionado muy bien para mí.Utilicé una expresión regular (simple, pero fea y grande) para localizar todas las palabras.La expresión es la siguiente:

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

Se muestra aquí con saltos de línea para fines de formato.

De todos modos, mi método fue ejecutar esta expresión regular con una biblioteca como PCRE y luego leer las coincidencias nombradas.Y funcionó en todos los diferentes ejemplos enumerados en esta pregunta, menos los tipos "La mitad", ya que no los agregué, pero como puede ver, no sería difícil hacerlo.Esto aborda muchos problemas.Por ejemplo, aborda los siguientes elementos de la pregunta original y otras respuestas:

  1. cardinal/nominal u ordinal:"uno" y "primero"
  2. errores ortográficos comunes:"cuarenta"/"cuarenta" (Tenga en cuenta que no aborda esto EXPLÍCITAMENTE, eso sería algo que le gustaría hacer antes de pasar la cadena a este analizador.Este analizador ve este ejemplo como "CUATRO"...)
  3. centenas/miles:2100 -> "veinte cien" y también "dos mil cien"
  4. separadores:"oncecientos cincuenta y dos", pero también "oncecientos cincuenta y dos" o "oncecientos cincuenta y dos" y otras cosas
  5. coloquialismos:"treinta y tantos" (Esto tampoco se aborda TOTALMENTE, ya que ¿qué ES "algo"?Bueno, este código encuentra este número simplemente como "30").**

Ahora, en lugar de almacenar este monstruo de expresión regular en su fuente, estaba considerando construir esta expresión regular en tiempo de ejecución, usando algo como lo siguiente:

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 parte fácil aquí es que solo almacenamos las palabras que importan.En el caso de SEXTO, notarás que no hay una entrada para él, porque es simplemente un número normal con TH añadido...Pero personas como DOCE necesitan una atención diferente.

Bien, ahora tenemos el código para construir nuestra (fea) expresión regular, ahora simplemente lo ejecutamos en nuestras cadenas numéricas.

Una cosa que recomendaría es filtrar o comerse la palabra "Y".No es necesario y sólo genera otros problemas.

Entonces, lo que querrá hacer es configurar una función que pase las coincidencias nombradas para "Magnitud" a una función que observe todos los valores de magnitud posibles y multiplique su resultado actual por ese valor de magnitud.Luego, crea una función que analiza las coincidencias denominadas "Valor" y devuelve un int (o lo que esté usando), según el valor descubierto allí.

Todas las coincidencias de VALOR se AGREGAN a su resultado, mientras que las coincidencias de magnitud multiplican el resultado por el valor de magnitud.Entonces, Doscientos Cincuenta Mil se convierte en "2", luego "2 * 100", luego "200 + 50", luego "250 * 1000", terminando con 250000...

Sólo por diversión, escribí una versión vbScript de esto y funcionó muy bien con todos los ejemplos proporcionados.Ahora, no admite coincidencias con nombres, así que tuve que trabajar un poco más para obtener el resultado correcto, pero lo conseguí.La conclusión es que, si es una coincidencia de "VALOR", agréguela a su acumulador.Si coincide en magnitud, multiplica tu acumulador por 100, 1000, 1000000, 1000000000, etc...Esto le proporcionará algunos resultados bastante sorprendentes, y todo lo que tiene que hacer para ajustar cosas como "la mitad" es agregarlas a su RegEx, colocarles un marcador de código y manejarlas.

Bueno, espero que esta publicación ayude a ALGUIEN.Si alguien quiere, puedo publicar mediante el pseudocódigo vbScript con el que usé para probar esto; sin embargo, no es un código bonito ni un código de producción.

Si puedo..¿Cuál es el idioma final en el que se escribirá esto?¿C++ o algo así como un lenguaje de programación?La fuente de Greg Hewgill será de gran ayuda para ayudar a comprender cómo se combina todo esto.

Déjame saber si puedo ser de alguna otra ayuda.Lo siento, sólo sé inglés/americano, así que no puedo ayudarte con los otros idiomas.

Estaba convirtiendo declaraciones de ediciones ordinales de libros modernos tempranos (p. ej."2nd edition", "Editio quarta") a números enteros y necesitaba soporte para los ordinales 1-100 en inglés y ordinales 1-10 en algunas lenguas romances.Esto es lo que se me ocurrió 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

Intentar

  1. Abra una solicitud HTTP para "http://www.google.com/search?q=" + número + "+en+decimal".

  2. Analiza el resultado para tu número.

  3. Almacene en caché los pares número/resultado para analizar las solicitudes a lo largo del tiempo.

Un lugar para empezar a buscar es el biblioteca gnu get_date, que puede analizar casi cualquier Fecha textual en inglés en una marca de tiempo.Si bien no es exactamente lo que estás buscando, su solución a un problema similar podría proporcionar muchas pistas útiles.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top