【问题标题】:Converting words to numbers in PHP在 PHP 中将单词转换为数字
【发布时间】:2010-11-07 20:02:48
【问题描述】:

我正在尝试将写为单词的数值转换为整数。例如, “iPhone有二十三万七百八十三个应用程序” 会成为 “iPhone 作为 230783 个应用程序”

在我开始编码之前,我想知道是否存在任何用于此转换的函数/代码。

【问题讨论】:

    标签: php word numbers


    【解决方案1】:

    有很多页面讨论从数字到单词的转换。反方向没有那么多。我能找到的最好的是 Ask Yahoo 上的一些伪代码。请参阅http://answers.yahoo.com/question/index?qid=20090216103754AAONnDz 了解一个不错的算法:

    嗯,总的来说,您要做两件事:查找标记(转换为数字的单词)和应用语法。简而言之,您正在为一种非常有限的语言构建解析器。

    您需要的令牌是:

    功率:千、百万、十亿
    百:百
    十:二十、三十……九十
    单位:一、二、三、...九、
    特殊:十、十一、十二、……十九

    (删除任何“和”,因为它们没有意义。将连字符分成两个标记。即 65 应该被处理为“六十”“五”)

    标记字符串后,从右移到左。

    1. 从右侧抓取所有标记,直到您击中 POWER 或整个字符串。

    2. 在这些模式的停止点之后解析标记:

      特别
      十个
      单位
      十个单位
      单位百
      单位百特
      单位 10
      单位百单位
      单位一百十单位

      (假设此语法中不允许使用“一千七百”)

      这将为您提供号码的最后三位。

    3. 如果你停在整个字符串,你就完成了。

    4. 如果你停在一个功率,从第 1 步重新开始,直到你达到更高的功率或整个字符串。

    【讨论】:

    • 谢谢约翰!这个算法正是我想要的。我试图从左到右解析它,但这看起来更好。感谢您的帮助!
    • 我在下面添加了一个实现类似算法的答案。
    【解决方案2】:

    老问题,但对于遇到这个问题的其他人,我今天必须写一个解决方案。下面的方法与 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();
    }
    

    【讨论】:

    • 我发现这是一个非常强大且简洁的解决方案。做得好!我唯一的编辑是添加 'lakh' => '100000''crore' => '10000000',正如 joeberts 回答中 user132513 提到的那样。
    • 这不起作用的一个用例是,例如$data= 'five or ten'。这将返回 50。上面的答案适用于 OP。但是,必须考虑字符串具有“正确”的格式。就我而言,我试图从未经检查的字符串中删除数字,而不控制(或不知道)字符串可能是什么。用户有时会在表单中输入一些非常奇怪的回复!
    • "无效位(例如拼写错误的数字)将被忽略,因此认为无效字符串的输出未定义";不幸的是,这仅用于转换包含单个数字的字符串。您可以尝试使用上面的$data 列表将字符串拆分为片段(因为这些是我们关心的唯一子字符串),然后在每个片段上运行它,然后使用拆分词组合结果。
    • @ElYobo 效果很好,除了如果 $data 值是 Ten 而不是 ten 则返回0 而不是 10 。请帮助这个区分大小写的伙伴。
    • 我建议将其作为第一行:$data = strtolower(trim($data));。这解决了@RajaGopal 提出的观点
    【解决方案3】:

    我没有对此进行过广泛的测试,我或多或少只是在研究它,直到我在输出中看到我所期望的,但它似乎有效,并且从左到右解析。

    <?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";
    
    ?>
    

    【讨论】:

    • 发现一个漏洞,漏掉了常见的数字和单词混合,例如“200万”。
    • 它还会混淆日期的某些措辞。 “我是十九八十一岁出生的”
    • 非常感谢 Joebert 的代码!我会努力改进它。我已经建立了一个包含 10000 个随机数字单词的测试集(使用 Numbers_Words),目前,将单词解码为数字的准确率为 75%。正确:45554 变成 45554 错误:51586 变成 586
    • 刚刚意识到这个问题。访问第一个键时发生了一些有趣的事情,即 'one' 而是将 'quadrillion' => '1000000000000000' 放在 'one' 之前,它可以 100% 的准确度。
    • 另外,在 $keys 中包含 'lakh' => '100000' 和 'crore' => '10000000'。在南亚国家,它们比百万更常见
    【解决方案4】:

    更新了El Yobo 的答案,现在可以在(几乎)任何包含数字的字符串上运行 wordsToNumber 函数。

    https://github.com/thefish/words-to-number-converter

    converter.php - 转换器本身

    test.php - 使用各种字符串进行测试

    UPD 22.10.2020:答案变得太大而无法维护。将代码移至 github。

    【讨论】:

    • 对于地址号码“一二三主要街道”或“一二十三主要街道”,这比其他方法好得多。
    • @thefish 喜欢混合输入支持,但是像“三万三千五十九美元”这样的字符串不起作用。 “和”会导致问题。
    【解决方案5】:

    我发现的最简单的方法是使用 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):

    【讨论】:

      【解决方案6】:

      PEAR Numbers_Words 包可能是一个好的开始:http://pear.php.net/package-info.php?package=Numbers_Words

      【讨论】:

      • 谢谢贾尼。这个包看起来很有趣,尽管这与我的目标相反,即从数字到单词。在未来的项目中会很有用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多