PHP 求助 阿拉伯数字转中文数字

@Ta 2022-04-25发布,2022-04-25修改 11151点击
被禁言
用户被禁言,发言自动屏蔽。
回复列表(27|隐藏机器人聊天)
  • @Ta / 2022-04-25 / /
    被禁言
    用户被禁言,发言自动屏蔽。
  • @Ta / 2022-04-26 / /

    贴个Python实现(简化了负数、小数)

    import re
    from itertools import chain
    
    
    def convert_to_chinese(n: int):
        if n <= 0: return '零'
        s = ''.join(chain.from_iterable(zip('%029d' % n, 'JCBAICBAHCBAGCBAFCBAECBADCBA '))).rstrip()
        b = re.sub(
            r'^[^1-9]*|(?P<z1>0)[0ABC]*(?:(?=[1-9])|(?P<_z1>(?=[D-J]|$)))|'
            r'(?P<b>[D-J])(?P<z2>0)[0A-L]*(?:(?=[1-9])|(?P<_z2>$))',
            lambda m: f"{m['b'] or ''}{m['_z2'] is None and m['z2'] or ''}" or (m['_z1'] is None and m['z1'] or ''),
            s,
        )
        r = re.sub('.', lambda m: '零一二三四五六七八九空空空空空空空十百千万亿兆京垓秭穰'[ord(m[0]) - ord('0')], b)
        return r
    
    
    if __name__ == '__main__':
        for i in 100, 101, 120217, 812368, 1024842940147:
            print(i, convert_to_chinese(i))
    

    输出:

    100 一百
    101 一百零一
    120217 一十二万零二百一十七
    812368 八十一万二千三百六十八
    1024842940147 一兆零二百四十八亿四千二百九十四万零一百四十七
    

    参考了网上大佬的C#代码(如下),

    然而Python的正则库不支持平衡组(看下PHP手册,好像也不支持),也不支持重复命名组,所以上面代码要额外写逻辑判断来模拟

    public static string ConvertToChinese(decimal number)
    {
        string s = number.ToString("#L#E#D#C#K#E#D#C#J#E#D#C#I#E#D#C#H#E#D#C#G#E#D#C#F#E#D#C#.@");
        string d = Regex.Replace(s, @"(((?<=-)|(?!-)^)[^1-9]*)|((?'z'0)[0A-E]*((?=[1-9])|(?'-z'(?=[F-L\.]|$))))|((?'b'[F-L])(?'z'0)[0A-L]*((?=[1-9])|(?'-z'(?=[\.]|$))))", "${b}${z}");
        string r = Regex.Replace(d, ".", m => "负元空零壹贰叁肆伍陆柒捌玖空空空空空空整分角拾佰仟万亿兆京垓秭穰"[m.Value[0] - '-'].ToString());
        return r;
    }
    
  • @Ta / 2022-04-25 / /
    被禁言
    用户被禁言,发言自动屏蔽。
  • @Ta / 2022-04-26 / /

    @无期徒刑,我在regex101翻了翻,好像就C#支持平衡组,其他语言只能写得丑一点咯

  • @Ta / 2022-04-26 / /

    @无期徒刑,若是你要求效率的话,我总感觉正则有点重,不如自己数组遍历(没测试过,懒得写。。)

    是嘛@老虎会游泳,转个数字上正则,会不会有点过重

  • @Ta / 2022-04-26 / /
  • @Ta / 2022-04-26 / /
    function toChineseNum($num) {
        $cnums = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
        $units = ["十", "百", "千", "万", "十", "百", "千", "亿"];
        $xs = array();
        foreach (array_reverse(str_split($num)) as $x) {
            $l = count($xs);
            if ($x != "0" || !($l % 4)) $n = ($x == '0'?'':$x) . ($units[($l-1) % count($units)]);
            else $n = is_numeric($xs[0][0])?$x:'';
            array_unshift($xs, $n);
        } 
        return implode("", str_replace(array_keys($cnums), $cnums, $xs));
    } 
    $num = 1024842940147 ;
    echo toChineseNum($num);
    
  • @Ta / 2022-04-26 / /

    @无名啊,性能好不好,只有自己计时对比才能知道。很多时候自己感觉更快的代码实际上更慢。

  • @Ta / 2022-04-26 / /
    被禁言
    用户被禁言,发言自动屏蔽。
  • @Ta / 2022-04-26 / /

    @无期徒刑,试过那个大佬的正则了吗?速度咋样

  • @Ta / 2022-04-26 / /
    被禁言
    用户被禁言,发言自动屏蔽。
  • @Ta / 2022-04-28 / /

    [22-04-28] 更新:反正可读性贼差,再精简一点儿来提升5%~10%速度

    [22-04-27] 更新:加上注释,怕以后看不懂

    @无期徒刑,实现了个看起来别扭的PHP版本(不熟PHP,可能还有优化空间)

    const mini_units = ['', '十', '百', '千'];
    const units = ['', '万', '亿', '兆', '京', '垓', '秭', '穰'];
    const nums = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
    
    function convert_to_chinese(int $n): string
    {
        if ($n <= 0) return '零';
    
        $r = '';
        $d = strrev((string)$n); // 反转字串,使得低位数字也是小索引值
    
        for ($l = strlen($d) - 1, $i = $l; $i >= 0;) {
    
            // 输出当前位置“数字”和“单位”。开头“一十”和“0”只输出单位,个位不输出单位
            $r .= nums[(int)$d[$i]] . ($i & 3 ? mini_units[$i & 3] : units[$i >> 2]);
    
            // 万/亿/兆…时,跳过后续的“0”(若有),停留在第一个非“0”处 或 结束处,在下次循环输出该处数字和单位
            if (!($i & 3) && $d[$i - 1] === '0')
                // 只要结束前碰到非“0”,就将前面连续跳过的“0”,替换为这一个“零”
                while (--$i >= 0 && ($d[$i] === '0' || !($r .= '零')));
    
            // 由此处驱动至下一个数字。跳过后续的“0”百(万/亿/兆/…)、“0”十(万/亿/兆/…),
            // 停留在第一个非“0”处 或 个/万/亿/兆/…处(最早出现为准),在下次循环输出该处数字和单位
            elseif (--$i >= 0 && $d[$i] === '0')
                // 停留在“0”个/万/亿/兆/…时不会输出“零”
                while ($i & 3 && ($d[--$i] === '0' || !($r .= '零')));
        }
    
        return $r;
    }
    
    while ($s = fgets(STDIN)) {
        $n = (int)$s;
        printf("%d %s\n", $n, convert_to_chinese($n));
    }
    

    输出如下(总计65536个数字,预先缓存至文件应该还能再快些。另外,WSL这个时间看起来怪怪的)

    $ time python3 -c "for i in range(1 << 16): print(f'{i:b}')" | php main.php | cat -n | sed $'1,6p; 1h; 2,5{H;d}; 6{H;g;i\\\n...\n}; $q; N; D'
         1  0 零
         2  1 一
         3  10 一十
         4  11 一十一
         5  100 一百
         6  101 一百零一
    ...
     65531  1111111111111010 一千一百一十一兆一千一百一十一亿一千一百一十一万一千零一十
     65532  1111111111111011 一千一百一十一兆一千一百一十一亿一千一百一十一万一千零一十一
     65533  1111111111111100 一千一百一十一兆一千一百一十一亿一千一百一十一万一千一百
     65534  1111111111111101 一千一百一十一兆一千一百一十一亿一千一百一十一万一千一百零一
     65535  1111111111111110 一千一百一十一兆一千一百一十一亿一千一百一十一万一千一百一十
     65536  1111111111111111 一千一百一十一兆一千一百一十一亿一千一百一十一万一千一百一十一
    
    real    0m0.461s
    user    0m0.406s
    sys     0m0.656s
    
  • @Ta / 2022-04-26 / /

    @无期徒刑,哦,还有那几个数字没测

    $ xargs -n1 <<<'100 101 120217 812368 1024842940147' | php main.php
    100 一百
    101 一百零一
    120217 一十二万零二百一十七
    812368 八十一万二千三百六十八
    1024842940147 一兆零二百四十八亿四千二百九十四万零一百四十七
    
  • @Ta / 2022-04-26 / /

    @无期徒刑,和我之前的Python版本结果一致

    $ diff <(python3 main.py <nums.txt) <(php main.php <nums.txt)
    $
    
  • @Ta / 2022-04-26 / /
    被禁言
    用户被禁言,发言自动屏蔽。
  • @Ta / 2022-04-28 / /

    [22-04-28] 修改以配合上面的精简

    @无期徒刑

    改成这样,可以将开头的一十变为,如一十二变为十二

    // 输出当前位置“数字”和“单位”。开头“一十”和“0”只输出单位,个位不输出单位
    $r .= ($i < $l || ($i & 3) !== 1 || $d[$i] !== '1' ? nums[(int)$d[$i]] : '') . ($i & 3 ? mini_units[$i & 3] : units[$i >> 2]);
    

    测试通过

    $ diff <(python3 main.py <nums.txt | sed 's/ 一十/ 十/') <(php main.php <nums.txt)
    $
    

    “二”和“两”没找到确切规律,就不改了

    一般在“千、万、亿”前用“两”

    二/两百亿零两千零二/两万?

  • @Ta / 2022-04-27 / /

    @无期徒刑,用PHP实现了那段正则,还好PHP支持重复命名组,比Python省了很多事

    define('unnamed', mb_str_split('零一二三四五六七八九空空空空空空空十百千万亿兆京垓秭穰'));
    
    function convert_to_chinese_by_regex(int $n): string
    {
        if ($n <= 0) return '零';
        $i = 0;
        $n = sprintf('%029d', $n);
        $s = preg_replace_callback(
            '/#/',
            function ($m) use ($n, &$i) {return $n[$i++];},
            '#J#C#B#A#I#C#B#A#H#C#B#A#G#C#B#A#F#C#B#A#E#C#B#A#D#C#B#A#',
        );
        $d = preg_replace_callback(
            '/(?J)^[^1-9]*|(?<z>0)[0ABC]*(?:(?=[1-9])|(?<z>(?=[D-J]|$)))|'.
            '(?<b>[D-J])(?<z>0)[0A-L]*(?:(?=[1-9])|(?<z>$))/',
            fn($m) => ($m['b'] ?? '') . ($m['z'] ?? ''),
            $s,
        );
        $r = preg_replace_callback(
            '/./',
            fn($m) => unnamed[ord($m[0]) - ord('0')],
            $d,
        );
    
        return $r;
    }
    

    但用时不是很可观,是纯数组版本耗时四五倍左右。。

    $ time php main.php < 65536_nums.txt >/dev/null
    
    real    0m2.182s
    user    0m2.016s
    sys     0m0.172s
    

    之前几个数字的测试:

    $ xargs -n1 <<<"100 101 120217 812368 1024842940147 $(((1 << 63) - 1))" | php main.php
    100 一百
    101 一百零一
    120217 一十二万零二百一十七
    812368 八十一万二千三百六十八
    1024842940147 一兆零二百四十八亿四千二百九十四万零一百四十七
    9223372036854775807 九百二十二京三千三百七十二兆零三百六十八亿五千四百七十七万五千八百零七
    

    Python版输出一致:

    $ diff <(python3 main.py <65536_nums.txt) <(php main.php <65536_nums.txt)
    $
    
  • @Ta / 2022-04-27 / /
    被禁言
    用户被禁言,发言自动屏蔽。
添加新回复
回复需要登录