我正在尝试将写为单词的数值转换为整数。例如,“iPhone 有 230783 个应用程序”将变为“iPhone 作为 230783 个应用程序”
在我开始编码之前,我想知道这个转换是否存在任何函数/代码。
有很多页面讨论从数字到单词的转换。反方向没有那么多。我能找到的最好的是 Ask Yahoo 上的一些伪代码。请参阅http://answers.yahoo.com/question/index?qid=20090216103754AAONnDz了解一个不错的算法:
好吧,总的来说,您正在做两件事:查找标记(转换为数字的单词)和应用语法。简而言之,您正在为一种非常有限的语言构建解析器。
您需要的令牌是:
POWER:千、百万、十亿
HUNDRED:一百
十:二十、三十……九十
单位:一、二、三、……九、
特殊:十、十一、十二、……十九(删除任何“和”,因为它们没有意义。将连字符分成两个标记。即 65 应处理为“六十”“五”)
一旦你标记了你的字符串,从右移到左。
抓住右边的所有标记,直到你击中一个 POWER 或整个字符串。
在这些模式的停止点之后解析标记:
特殊
十
单元
十单元
百
单元 百特殊
单元 百十
单元 百单元
单元 百十单元(这里假设这个语法中不允许使用“一千七百”)
这将为您提供号码的最后三位数字。
如果你停在整个字符串,你就完成了。
如果你停在一个功率,从第 1 步重新开始,直到你达到更高的功率或整个字符串。
老问题,但是对于遇到这个问题的其他人,我今天不得不写一个解决方案。下面的方法与 John Kugelman 描述的算法有一种模糊相似的方法,但并不适用于严格的语法;因此,它将允许一些奇怪的排序,例如“十万和一百万”仍然会产生与“一百万和十万”(1,100,000)相同的结果。无效位(例如拼写错误的数字)将被忽略,因此将无效字符串上的输出视为未定义。
根据 user132513 对 joebert 回答的评论,我使用 Pear 的 Number_Words 生成测试系列。以下代码在 0 到 5,000,000 之间的数字上得分 100%,然后在 0 到 10,000,000 之间的 100,000 个数字的随机样本上得分 100%(运行整个 100 亿个系列需要很长时间)。
/**
* Convert a string such as "one hundred thousand" to 100000.00.
*
* @param string $data The numeric string.
*
* @return float or false on error
*/
function wordsToNumber($data) {
// Replace all number words with an equivalent numeric value
$data = strtr(
$data,
array(
'zero' => '0',
'a' => '1',
'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',
'fourty' => '40', // common misspelling
'fifty' => '50',
'sixty' => '60',
'seventy' => '70',
'eighty' => '80',
'ninety' => '90',
'hundred' => '100',
'thousand' => '1000',
'million' => '1000000',
'billion' => '1000000000',
'and' => '',
)
);
// Coerce all tokens to numbers
$parts = array_map(
function ($val) {
return floatval($val);
},
preg_split('/[\s-]+/', $data)
);
$stack = new SplStack; // Current work stack
$sum = 0; // Running total
$last = null;
foreach ($parts as $part) {
if (!$stack->isEmpty()) {
// We're part way through a phrase
if ($stack->top() > $part) {
// Decreasing step, e.g. from hundreds to ones
if ($last >= 1000) {
// If we drop from more than 1000 then we've finished the phrase
$sum += $stack->pop();
// This is the first element of a new phrase
$stack->push($part);
} else {
// Drop down from less than 1000, just addition
// e.g. "seventy one" -> "70 1" -> "70 + 1"
$stack->push($stack->pop() + $part);
}
} else {
// Increasing step, e.g ones to hundreds
$stack->push($stack->pop() * $part);
}
} else {
// This is the first element of a new phrase
$stack->push($part);
}
// Store the last processed part
$last = $part;
}
return $sum + $stack->pop();
}
我没有对此进行过广泛的测试,我或多或少只是在研究它,直到我在输出中看到我所期望的,但它似乎有效,并且从左到右解析。
<?php
$str = 'twelve billion people know iPhone has two hundred and thirty thousand, seven hundred and eighty-three apps as well as over one million units sold';
function strlen_sort($a, $b)
{
if(strlen($a) > strlen($b))
{
return -1;
}
else if(strlen($a) < strlen($b))
{
return 1;
}
return 0;
}
$keys = array(
'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'
);
preg_match_all('#((?:^|and|,| |-)*(\b' . implode('\b|\b', array_keys($keys)) . '\b))+#i', $str, $tokens);
//print_r($tokens); exit;
$tokens = $tokens[0];
usort($tokens, 'strlen_sort');
foreach($tokens as $token)
{
$token = trim(strtolower($token));
preg_match_all('#(?:(?:and|,| |-)*\b' . implode('\b|\b', array_keys($keys)) . '\b)+#', $token, $words);
$words = $words[0];
//print_r($words);
$num = '0'; $total = 0;
foreach($words as $word)
{
$word = trim($word);
$val = $keys[$word];
//echo "$val\n";
if(bccomp($val, 100) == -1)
{
$num = bcadd($num, $val);
continue;
}
else if(bccomp($val, 100) == 0)
{
$num = bcmul($num, $val);
continue;
}
$num = bcmul($num, $val);
$total = bcadd($total, $num);
$num = '0';
}
$total = bcadd($total, $num);
echo "$total:$token\n";
$str = preg_replace("#\b$token\b#i", number_format($total), $str);
}
echo "\n$str\n";
?>
稍微更新了 El Yobo 的答案,现在可以在(几乎)任何包含数字的字符串上运行 wordsToNumber 函数。
https://github.com/thefish/words-to-number-converter
converter.php - 转换器本身
test.php - 使用各种字符串进行测试
UPD 22.10.2020:答案变得太大而无法维护。将代码移至 github。
我发现最简单的方法是使用numfmt_parse:
$fmt = numfmt_create('en_US', NumberFormatter::SPELLOUT);
echo numfmt_parse($fmt, 'one million two hundred thirty-four thousand five hundred sixty-seven');
(来源;Dorian 在https://stackoverflow.com/a/31588055/11827985的帖子):
PEARNumbers_Words
包可能是一个好的开始:http ://pear.php.net/package-info.php?package=Numbers_Words