如何使用ICU解析汉字数字字符?

8
我正在使用ICU编写一个函数来解析由汉字数字字符组成的Unicode字符串,并希望返回该字符串的整数值。
例如: "五" => 5 "三十一" => 31 "五千九百七十二" => 5972
我将区域设置为Locale::getJapan(),并使用NumberFormat::parse()来解析字符字符串。但是,每当我传递任何汉字字符时,parse()方法都会返回U_INVALID_FORMAT_ERROR。
请问是否有人知道ICU是否支持在NumberFormat::parse()方法中解析汉字字符字符串?我希望由于我将区域设置为日语,它能够解析汉字数字值。
谢谢!
#include <iostream>
#include <unicode/numfmt.h>

using namespace std;

int main(int argc, char **argv) {
    const Locale &jaLocale = Locale::getJapan();
    UErrorCode status = U_ZERO_ERROR;
    NumberFormat *nf = NumberFormat::createInstance(jaLocale, status);

    UChar number[] = {0x4E94}; // Character for '5' in Japanese '五'
    UnicodeString numStr(number);
    Formattable formattable;
    nf->parse(numStr, formattable, status);
    if (U_FAILURE(status)) {
        cout << "error parsing as number: " << u_errorName(status) << endl;
        return(1);
    }
    cout << "long value: " << formattable.getLong() << endl;
}

1
我不知道,但这是一个很酷的问题,我期待着答案。 - Charlie Martin
你是在询问如何解决这个问题的算法吗?还是在询问如何获取字符编码以便进行解释(即编码问题)? - hasen
感谢所有的答案和评论!我想要澄清的是,ICU 是否能够正确解析包含汉字数字值的字符串,并将其返回为整数。由于使用了ICU,如果它能够实现这一点,那么我就不必编写自己的例程来处理此问题。我正在开发一个程序以支持不同的区域设置,并希望不必针对每个区域编写定制的例程。理想情况下,我只需要将区域设置和数据字符串传递给ICU,然后让它返回整数值。 - Aki
请查看“Steven R. Loomis”的答案,那是正确的。RBNF支持它。 - Artyom
4个回答

6
您可以使用 ICU 基于规则的数字格式 (RBNF) 模块 rbnf.h (C++) 或 C 中的 unum.h,使用 UNUM_SPELLOUT 选项,在日语区域设置 ("ja" locale) 下进行翻译。对于 C++,Atryom 提供了以下代码纠正:new RuleBasedNumberFormat(URBNF_SPELLOUT,jaLocale, status);

1
这是正确的答案:不要使用 NumberFormat::createInstance(jaLocale, status);,而应该使用 new RuleBasedNumberFormat(URBNF_SPELLOUT,jaLocale, status); - Artyom

3

我之前创建了一个小型的Perl模块来完成这个任务。它可以将阿拉伯语和日语互相转换,虽然我没有进行详尽的测试,但我认为它非常全面。欢迎您对其进行改进。

 
package kanjiArabic;
use strict;
use warnings;
our $VERSION = "1.00";
use utf8;

our %big = (
    十 => 10,百 => 100,千 => 1000,
    );
our %bigger = (
    万 => 10000,億 => 100000000,
    兆 => 1000000000000,京 => 10000000000000000,
    垓 => 100000000000000000000,
    );
#precompile regexes                                                                                                          
our $qr = qr/[0-9]/;
our $bigqr = qr/[十百千]/;
our $biggerqr = qr/[万億兆京垓]/;

#this routine does most of the real work.
sub kanji2arabic{
    $_ = shift;

    tr/〇一二三四五六七八九/0123456789/;
    #optionally precompile for performance boost                                                                             
    s/(?<=${qr})(${bigqr})/\*${1}/g;
    s/(?<=${bigqr})(${bigqr})/\+${1}/g;
    s/(${bigqr})(?=${qr})/${1}\+/g;
    s/(${bigqr})(?=${bigqr})/${1}\+/g;
    s/(${bigqr})/${big{$1}}/g;

    s/([0-9\+\*]+)/\(${1}\)/g;

    s/(? "〇", 1 => "一", 2 => "二", 3 => "三", 4 => "四",
    5 => "五", 6 => "六", 7 => "七", 8 => "八", 9 => "九",
    );
our %places = (
    1 => 10, 
    2 => 100, 
    3 => 1000, 
    4 => 10000, 
    8 => 100000000, 
    12 => 1000000000000,
    16 => 10000000000000000, 
    20 => 100000000000000000000,
    );
our %abig   = (
    10 => "十", 
    100 => "百", 
    1000 => "千", 
    10000 => "万", 
    100000000 => "億",
    1000000000000 => "兆", 
    10000000000000000 => "京", 
    100000000000000000000 => "垓",
    );
our $MAX = 24; #We only support numbers up to 24 digits!                                                                     


sub arabic2kanji{
    my @number = reverse(split(//,$_[0]));
    my @kanji;
    for(my $i=$#number;$i>=0;$i--){
        if( $i==0 ){push(@kanji,$asmall{$number[$i]});}
        elsif( $i % 4 == 0 ){
            if( $number[$i] !~ m/[01]/ ){
                push(@kanji,$asmall{$number[$i]});
            }
            push(@kanji,$abig{$places{$i}});
    }else{
            my $p = $i % 4;
            if( $number[$i]==0 ){
                next;
            }elsif( $number[$i]==1 ){
                push(@kanji,$abig{$places{$p}});
            }else{
                push(@kanji,$asmall{$number[$i]});
        push(@kanji,$abig{$places{$p}});
            }
    }
    }
    return join("",@kanji);
}


sub eval_k2a{
    #feed me utf-8!                                                                                                          
    if($_[0] !~ m/^[〇一二三四五六七八九十百千万億兆京垓]+$/){
        print "Error: ".$_[0].
              " not a Kanji number.\n" if defined($_[1])&&$_[1]==1;
        return -1;
    }
    my $expression = kanji2arabic($_[0]);
    print $expression."\n" if defined($_[1])&&$_[1]==1;
    return eval($expression);
}



1;

然后您可以从另一个脚本中这样调用它:


#!/usr/bin/perl -w
use strict;
use warnings;
use Encode;
use kanjiArabic;

my $kanji = kanjiArabic::arabic2kanji($ARGV[0]);
print "Kanji: ".encode("utf8",$kanji)."\n";
my $arabic =  kanjiArabic::eval_k2a($kanji);
print "Back to arabic...\n";
print "Arabic: ".$arabic."\n";

并且像这样使用此脚本:


kettle:~/k2a$ ./k2a.pl 5000215
Kanji: 五百万二百十五
Back to arabic...
Arabic: 5000215

继续加油。


1

你的问题让我受到启发,用Python 解决了这个问题

如果你没有找到C++的解决方案,那么将其适应到C++中也不会太难。


0

这实际上相当困难,特别是当你开始查看非常大的数字时,其中包含了一些晦涩的汉字。

在Perl中,有一个非常完整的实现Lingua::JA::Numbers。如果你想将它移植到C++,它的源代码可能会给你灵感。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接