Как читать значения из чисел, написанных как слова?
Как мы все знаем, цифры могут быть записаны либо в цифрах, либо вызваны их именами. Хотя есть много примеров, которые можно найти, чтобы преобразовать 123 в сто двадцать три, я не мог найти хороших примеров того, как конвертировать их по-другому.
Некоторые из оговорок:
- кардинал/номинальный или порядковый: "один" и "первый"
- общие орфографические ошибки: "сорок" / "четыре"
- сотни/тысячи: 2100 → "двадцать сто", а также "две тысячи сто".
- разделители: "одиннадцать сотен пятьдесят два", но также "одиннадцатьсот пятидесяти" или "одиннадцатьсот пятьдесят два" и "ничего"
- коллоиализмы: "тридцать-то"
- фрагменты: "одна треть", "две пятых"
- общие имена: 'дюжина', 'половина'
И, возможно, возможны дополнительные предостережения, которые еще не перечислены.
Предположим, что алгоритм должен быть очень надежным и даже понимать орфографические ошибки.
Какие поля/документы/исследования/алгоритмы следует читать, чтобы научиться писать все это?
Где информация?
PS: Мой последний парсер должен понимать 3 разных языка: английский, русский и иврит. И, возможно, на более поздней стадии будет добавлено больше языков. На иврите также есть мужские/женские числа, такие как "один мужчина" и "одна женщина" имеют разные "один", "эхад" и "ахат". У русского также есть свои сложности.
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)
Ответы
Ответ 1
Я играл с парсером 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
И так далее. Я не говорю, что это идеально, но для быстрого и грязного это хорошо.
Адресация вашего конкретного списка при редактировании:
- кардинальные/номинальные или порядковые: "один" и "первый" - просто помещают их в словарь
- english/british: "fourty" / "сорок" - ditto
- сотни/тысячи: 2100 → "двадцать сто", а также "две тысячи сто" - работает как
- разделители: "одиннадцатьсот пятьдесят два", но также "одиннадцатьсот пятидесяти" или "одиннадцатьсот пятьдесят два", а еще что - просто определяют "следующее слово" как самый длинный префикс, который соответствует определенному слову, или до следующего не-слова, если этого не делают, для начала
- colloqialisms: "тридцать-то" - работает
- фрагменты: "одна треть", "две пятых" - , но еще не...
- общие имена: "дюжина", "половина" - <сильная > работает; вы даже можете делать такие вещи, как "полдюжины"
Число 6 - единственное, на что у меня нет готового ответа, и что из-за двусмысленности между ординалами и фракциями (по крайней мере, на английском языке) добавляется тот факт, что моя последняя чашка кофе была много часов назад.
Ответ 2
Это непростая проблема, и я не знаю, как это сделать. Я мог бы сесть и попытаться написать что-то подобное. Я бы сделал это в Prolog, Java или Haskell. Насколько я вижу, есть несколько вопросов:
- Tokenization: иногда цифры записываются одиннадцатьсот пятьдесят два, но я видел одиннадцатьсот пятидесяти или одиннадцатьсот пятьдесят два и еще много чего. Можно было бы провести опрос о том, какие формы фактически используются. Это может быть особенно сложно для иврита.
- Ошибки орфографии: это не так сложно. У вас ограниченное количество слов, и немного магии Левенштейна должно сделать трюк.
- Альтернативные формы, как вы уже упоминали, существуют. Это включает в себя порядковые/кардинальные числа, а также сорок/четыре и...
- ... общие имена или часто используемые фразы и сетевые элементы (именованные объекты). Вы хотите извлечь 30 из Тридцатилетней войны или 2 из Второй мировой войны?
- Римские цифры тоже?
- Коллоиализмы, такие как "тридцать-то" и "три евро и шрапнель", которые я не знаю, как лечить.
Если вы заинтересованы в этом, я мог бы сделать это в эти выходные. Моя идея, вероятно, использует UIMA и токенизацию с ней, затем переходим к дальнейшему tokenize/disambiguate и, наконец, переводим. Там может быть больше проблем, посмотрим, смогу ли я придумать еще несколько интересных вещей.
Извините, это еще не настоящий ответ, просто расширение вашего вопроса. Я дам вам знать, если я найду/напишу что-нибудь.
Кстати, если вас интересует семантика цифр, я только что нашел интересную статью от Friederike Moltmann, обсудив некоторые вопросы, касающиеся логическая интерпретация цифр.
Ответ 3
У меня есть код, который я написал некоторое время назад: text2num. Это делает некоторые из того, что вы хотите, за исключением того, что оно не обрабатывает порядковые номера. Я вообще не использовал этот код для чего-либо, поэтому он в значительной степени не тестировался!
Ответ 4
Используйте библиотеку Python pattern-en:
>>> from pattern.en import number
>>> number('two thousand fifty and a half') => 2050.5
Ответ 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)
Ответ 6
Порядковые числа не применимы, потому что они не могут быть связаны значимыми способами с другими числами на языке (... по крайней мере, на английском языке)
например. сто первый, одиннадцать секунд и т.д.
Однако есть еще одно английское/американское предостережение со словом "и"
то есть.
сто один (английский)
сто один (американский)
Кроме того, использование "a" означает один на английском языке
тысяча = одна тысяча
... С другой стороны, калькулятор Google делает удивительную работу.
в сто три тысячи раз больше скорости света
И даже...
две тысячи сто и дюжина
... WTF?!? оценка плюс дюжина в римских цифрах
Ответ 7
Вот чрезвычайно надежное решение в Clojure.
AFAIK - это уникальный подход к реализации.
;----------------------------------------------------------------------
; numbers.clj
; written by: Mike Mattie [email protected]
;----------------------------------------------------------------------
(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"])
Ответ 8
Моя реализация 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;
}
Ответ 9
Хорошо, я слишком поздно ответил на этот вопрос, но я работал над небольшим тестовым сценарием, который, похоже, очень хорошо работал у меня. Я использовал (простое, но уродливое и большое) регулярное выражение, чтобы найти все слова для меня. Выражение выглядит следующим образом:
(?<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)))
Здесь показаны разрывы строк для целей форматирования.
В любом случае, мой метод состоял в том, чтобы выполнить этот RegEx с библиотекой, такой как PCRE, а затем прочитать именованные совпадения. И он работал на всех разных примерах, перечисленных в этом вопросе, минус "одна половина", типа, поскольку я их не добавлял, но, как вы можете видеть, это было бы непросто сделать. Это касается многих проблем. Например, он обращается к следующим вопросам в исходном вопросе и других ответах:
- кардинал/номинальный или порядковый: "один" и "первый"
- общие орфографические ошибки: "сорок" / "четыре" (обратите внимание на то, что это НЕ СПЕЦИАЛЬНО относится к этому вопросу, это будет то, что вы хотели бы сделать, прежде чем передать строку этому парсеру. Этот анализатор видит этот пример как "ЧЕТВЕРТЫЙ"...)
- сотни/тысячи: 2100 → "двадцать сто", а также "две тысячи сто".
- разделители: "одиннадцать сотен пятьдесят два", но также "одиннадцатьсот пятидесяти" или "одиннадцатьсот пятьдесят два" и "ничего"
- 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, теперь мы просто выполняем его на наших числовых строках.
Одна вещь, которую я бы рекомендовал, - это отфильтровать или съесть слово "И". Это не обязательно, и это приводит только к другим проблемам.
Итак, вам нужно настроить функцию, которая передает именованные совпадения для "Масштаб" в функцию, которая просматривает все возможные значения величины, и умножает ваш текущий результат на это значение величины. Затем вы создаете функцию, которая смотрит на совпадение "Значение", и возвращает int (или что-то еще, что вы используете), на основе найденного там значения.
Все совпадения VALUE добавляются к вашему результату, а magnitutde - умножают результат на значение mag. Итак, "Две сотни пятьдесят тысяч" становятся "2", затем "2 * 100", затем "200 + 50", затем "250 * 1000", заканчивая 250000...
Просто для удовольствия, я написал версию vbScript этого, и он отлично поработал со всеми приведенными примерами. Теперь он не поддерживает именованные совпадения, поэтому мне пришлось немного усложнить получение правильного результата, но я получил его. Итог, если это матч "VALUE", добавьте его в свой аккумулятор. Если это соответствует величине, умножьте свой аккумулятор на 100, 1000, 1000000, 1000000000 и т.д. Это даст вам некоторые довольно удивительные результаты, и все, что вам нужно сделать, чтобы настроить такие вещи, как "одна половина", это добавить их к вашему RegEx, поставьте маркер кода для них и обработайте их.
Ну, я надеюсь, что этот пост поможет кому-то там. Если кто-то захочет, я могу опубликовать псевдо-код vbScript, который я использовал для проверки этого, но это не очень красивый код и НЕ производственный код.
Если я могу.. Каков окончательный язык, на котором это будет написано? С++, или что-то вроде скриптового языка? Источник Грега Хьюглилла поможет вам понять, как все это происходит вместе.
Сообщите мне, могу ли я оказать любую другую помощь. Извините, я знаю только английский/американский, поэтому я не могу помочь вам с другими языками.
Ответ 10
Я преобразовывал порядковые редакционные заявления ранних современных книг (например, "2-е издание", "Редактирование кварты" ) в целые числа и нуждался в поддержке ординалов 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
Ответ 11
Try
-
Откройте HTTP-запрос на " http://www.google.com/search?q=" + number + "+ in + decimal".
-
Разберите результат для своего номера.
-
Кэшировать пары число/результат для усреднения запросов с течением времени.
Ответ 12
Одно место для поиска - gnu get_date lib, который может анализировать около любой текстовой даты в текстовую метку. Хотя это не совсем то, что вы ищете, их решение подобной проблемы может обеспечить множество полезных подсказок.