2

OCR エンジンを使用して紙の文書を認識するシステムに取り組んでいます。これらのドキュメントは、合計、付加価値税、正味額などの金額を含む請求書です。これらの金額の文字列を数値に解析する必要がありますが、各請求書の数値の小数点と千の区切りにさまざまな記号を使用するさまざまな形式とフレーバーがあります。.NET で通常の double.tryparse および double.parse メソッドを使用しようとすると、通常、一部の量で失敗します。

これらは私が量として受け取る例の一部です

"3.533,65" =>  3533.65 
"-133.696" => -133696
"-33.017" => -33017
"-166.713" => -166713
"-5088,8" => -5088.8 
"0.423" => 0.423
"9,215,200" => 9215200
"1,443,840.00" => 1443840

数値の小数点記号と桁区切り記号が何であるかを推測し、ユーザーに値を提示して、これが正しいかどうかを判断する方法が必要です。

この問題をエレガントな方法で解決する方法を考えています。

4

8 に答える 8

9

データの出所がわからないと、常にあいまいになるため、これをエレガントに理解できるかどうかはわかりません。

たとえば、数値 1.234 と 1,234 はどちらも有効な数値ですが、記号の意味を確立しないと、どちらがどちらであるかを判断できません。

個人的には、いくつかのルールに基づいて「最善の推測」を試みる関数を作成します...

  • 数値に,BEFOREが含まれている場合は.,1000 単位で、10.進数単位である必要があります。
  • 数値に.BEFOREが含まれている場合は,.1000 単位で、10,進数単位である必要があります。
  • シンボルが 1 つ以上ある場合,、千区切り記号は,
  • シンボルが 1 つ以上ある場合.、千区切り記号は.
  • 1 しかない場合、,それに続く数字はいくつですか? 3 でない場合は、小数点記号でなければなりません ( についても同じルール.) 。
  • 3 つの数字で区切られている場合 (例: 1,234 と 1.234)、おそらくこの数字を脇に置いて、同じページの他の数字を解析して、それらが異なる区切り記号を使用しているかどうかを調べてから、それに戻ることができますか?

小数点の区切りを見つけたら、桁区切り記号 (数値の解析には必要ありません) を削除し、小数点区切り記号が であることを確認します。解析している文字列で。次に、これをに渡すことができますDouble.TryParse

于 2009-12-08T14:26:09.863 に答える
7

おそらく、優先順に指定されたルールのリストを設定するでしょう。このようにして、優先順位に従ってルールをプラグインできます。次に、正しいルールを返す正規表現の一致に基づいてリストを解析できます。

簡単なプロトタイプは、次のように非常に簡単にセットアップできます。

public class FormatRule
{
    public string Pattern { get; set; }
    public CultureInfo Culture { get; set; }

    public FormatRule(string pattern, CultureInfo culture)
    {
        Pattern = pattern;
        Culture = culture;
    }
}

ルールを優先順に保存するために使用される FormatRule のリスト:

List<FormatRule> Rules = new List<FormatRule>()
{
    /* Add rules in order of precedence specifying a culture
     * that can handle the pattern, I've chosen en-US and fr-FR
     * for this example, but equally any culture could be swapped
     * in for various formats you may need to use */
    new FormatRule(@"^0.\d+$", CultureInfo.GetCultureInfo("en-US")),
    new FormatRule(@"^0,\d+$", CultureInfo.GetCultureInfo("fr-FR")),
    new FormatRule(@"^[1-9]+.\d{4,}$", CultureInfo.GetCultureInfo("en-US")),
    new FormatRule(@"^[1-9]+,\d{4,}$", CultureInfo.GetCultureInfo("fr-FR")),
    new FormatRule(@"^-?[1-9]{1,3}(,\d{3,})*(\.\d*)?$", CultureInfo.GetCultureInfo("en-US")),
    new FormatRule(@"^-?[1-9]{1,3}(.\d{3,})*(\,\d*)?$", CultureInfo.GetCultureInfo("fr-FR")),

    /* The default rule */
    new FormatRule(string.Empty, CultureInfo.CurrentCulture)
}

その後、適用する正しいルールを探してリストを反復できるはずです。

public CultureInfo FindProvider(string numberString)
{
    foreach(FormatRule rule in Rules)
    {
        if (Regex.IsMatch(numberString, rule.Pattern))
            return rule.Culture;
    }
    return Rules[Rules.Count - 1].Culture;
}

この設定により、ルールを簡単に管理し、何らかの方法で処理する必要がある場合に優先順位を設定できます。また、異なるカルチャを指定して、ある形式をある方法で処理し、別の形式を別の方法で処理することもできます。

public float ParseValue(string valueString)
{
    float value = 0;
    NumberStyles style = NumberStyles.Any;
    IFormatProvider provider = FindCulture(valueString).NumberFormat;
    if (float.TryParse(numberString, style, provider, out value))
        return value;
    else
        throw new InvalidCastException(string.Format("Value '{0}' cannot be parsed with any of the providers in the rule set.", valueString));
}

最後に、ParseValue() メソッドを呼び出して、持っている文字列値を float に変換します。

string numberString = "-123,456.78"; //Or "23.457.234,87"
float value = ParseValue(numberString);

辞書を使用して、追加の FormatRule クラスを節約することもできます。概念は同じです...LINQを使用してクエリを実行しやすくするため、例でリストを使用しました。また、必要に応じて、single、double、または decimal に使用した float 型を簡単に置き換えることができます。

于 2009-12-08T16:35:31.777 に答える
3

小数点記号と桁区切り記号を推測するには、独自の関数を作成する必要があります。次に、対応する CultureInfo を使用して double.Parse を実行できます。

私はこのようなことをすることをお勧めします (つまり、これは本番環境でテストされた関数ではありません):

private CultureInfo GetNumbreCultureInfo(string number)
    {
        CultureInfo dotDecimalSeparator = new CultureInfo("En-Us");
        CultureInfo commaDecimalSeparator = new CultureInfo("Es-Ar");

        string[] splitByDot = number.Split('.');
        if (splitByDot.Count() > 2) //has more than 1 . so the . is the thousand separator
            return commaDecimalSeparator; //return a cultureInfo where the thousand separator is the .

        //the same for the ,
        string[] splitByComma = number.Split(',');
        if (splitByComma.Count() > 2)
            return dotDecimalSeparator;

        //if there is no , or . return an invariant culture
        if (splitByComma.Count() == 1 && splitByDot.Count() == 1)
            return CultureInfo.InvariantCulture;

        //if there is only 1 . or 1 , lets check witch is the last one
        if (splitByComma.Count() == 2)
            if (splitByDot.Count() == 1)
                if (splitByComma.Last().Length != 3) // , its a decimal separator
                    return commaDecimalSeparator;
                else// here you dont really know if its the dot decimal separator i.e 100.001 this can be thousand or decimal separator
                    return dotDecimalSeparator;
            else //here you have something like 100.010,00 ir 100.010,111 or 100,000.111
            {
                if (splitByDot.Last().Length > splitByComma.Last().Length) //, is the decimal separator
                    return commaDecimalSeparator;
                else
                    return dotDecimalSeparator;
            }
        else
            if (splitByDot.Last().Length != 3) // . its a decimal separator
                return dotDecimalSeparator;
            else
                return commaDecimalSeparator; //again you really dont know here... i.e. 100,101
    }

次のような簡単なテストを実行できます。

string[] numbers = { "100.101", "1.000.000,00", "100.100,10", "100,100.10", "100,100.100", "1,00" };

        decimal n;
        foreach (string number in numbers)
        {
            if (decimal.TryParse(number, NumberStyles.Any, GetNumbreCultureInfo(number), out n))
                MessageBox.Show(n.ToString());//the decimal was parsed
            else
                MessageBox.Show("there was problems parsing");
        }

また、魔女が区切り記号 (100,010 や 100.001 など) であることがよくわからない場合は、小数点または千単位の区切り記号を使用できます。

これを保存して、魔女がドキュメントのカルチャであることを知るために必要なデータ量をドキュメントで確認し、そのカルチャを保存して、常に同じカルチャを使用することができます (ドキュメントがすべて同じカルチャにあると仮定できる場合)。 ...)

これが役立つことを願っています

于 2009-12-08T15:00:17.147 に答える
2

発生する可能性のあるさまざまなケースを定義し、各着信文字列をケースの1つに一致させるロジックを作成してから、適切なFormatProviderを指定して解析する必要があります。たとえば、文字列にコンマの前に小数点が含まれている場合、この特定の文字列では、小数点を千単位の区切り文字として使用し、コンマを小数点記号として使用していると想定できるため、フォーマットプロバイダーを作成できます。このシナリオに対処するため。

これらの線に沿って何かを試してください:

public IFormatProvider GetParseFormatProvider(string s) {
  var nfi = new CultureInfo("en-US", false).NumberFormat;
  if (/* s contains period before comma */) {
    nfi.NumberDecimalSeparator = ",";
    nfi.NumberGroupSeparator = ".";
  } else if (/* some other condition */) {
     /* construct some other format provider */
  }
  return(nfi);
}

次に、Double.Parse(myString、GetParseFormatProvider(myString))を使用して実際の解析を実行します。

于 2009-12-08T14:31:25.877 に答える
2

でそれができるはずですDouble.TryParse。私が見ているあなたの最大の問題は、数字の解釈方法に矛盾があることです.

たとえば、どのように

"-133.696" => -133696  

いつ

"-166.713" => -166.713

?

于 2009-12-08T14:21:02.277 に答える
2

数値を変換するための規則が一貫していない場合、コードでこれを解決することはできません。klausbyskov が指摘したように、「-133.696」のピリオドが「-166.713」のピリオドと異なる意味を持つのはなぜですか? これらの 2 つの例を考えると、小数点を含む数値をどう処理すればよいかを知るにはどうすればよいでしょうか。

于 2009-12-08T14:29:09.673 に答える
1

「次に、値をユーザーに提示して、これが正しいかどうかを判断します。」

複数の可能性がある場合は、両方をユーザーに表示してみませんか?

処理できるようにしたいさまざまなカルチャでTryParseを呼び出す複数のメソッドを作成し、リストで成功する(重複を削除する)メソッドの解析結果を収集できます。

さまざまな形式がドキュメントの他の場所で使用されている頻度に基づいて、さまざまな可能性が正しい可能性を推定し、正しい可能性でソートされたリストに代替案を提示することもできます。たとえば、3,456,231.4のような数字をすでにたくさん見た場合、同じドキュメントの後半で4,675を見ると、おそらくコンマが数千の区切り文字であり、リストの最初に「4675」、2番目に「4.675」と表示されていると推測できます。 。

于 2009-12-08T14:41:25.623 に答える
0

ドットまたはコンマの後に 2 桁以内の数字が続く場合、それは小数点です。それ以外の場合は、無視してください。

于 2009-12-08T15:03:58.493 に答える