سؤال

أنا أعمل على نظام يتعرف على وثائق الورق باستخدام محركات التعرف الضوئي على الحروف. هذه المستندات هي الفواتير التي تحتوي على كميات مثل المجموع وضريبة القيمة المضافة والمبالغ الصافية. أحتاج إلى تحليل هذه السلاسل في أرقام، لكنها تأتي في العديد من الأشكال والنكهات باستخدام رموز مختلفة للانفصال العشري والآلاف في الرقم في كل فاتورة. إذا كنت أحاول استخدام أساليب Double.tryParse العادية وسلطات مزدوجة. ثم تفشل عادة في بعض المبالغ

هذه بعض الأمثلة التي أتلقاها كمبلغ

"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

أحتاج إلى بعض الطرق لتخمين ما هو الفاصل العشري وفصل الألف في الرقم ثم تقديم القيمة للمستخدم ليقرر ما إذا كان هذا صحيحا أم لا.

أتساءل كيف حل هذه المشكلة بطريقة أنيقة.

هل كانت مفيدة؟

المحلول

من المحتمل أن أقوم بإعداد قائمة من القواعد المحددة بترتيب الأفضلية، وبهذه الطريقة يمكنك توصيل القواعد بالأبقيقة. يمكنك بعد ذلك تحليل القائمة بناء على مطابقات Regex بإرجاع القاعدة الصحيحة.

سيكون النموذج الأولي السريع سهل للغاية لإعداده:

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 () لتحويل قيمة السلسلة، يجب عليك تعويم:

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

قد تقرر استخدام القاموس للحفظ على فئة FormatRule إضافية؛ المفهوم هو نفسه ... استخدمت قائمة في المثال لأنه يجعل من الأسهل استخدام الاستعلام LinQ. أيضا، يمكنك بسهولة استبدال نوع العائم الذي استخدمته للأفردة أو المزدوجة أو العشرية إذا لزم الأمر.

نصائح أخرى

لست متأكدا من أنك ستكون قادرا على الحصول على طريقة أنيقة لمعرفة ذلك، لأنها ستكون دائما غاب إذا كنت لا تستطيع أن تخبرها حيث تكون البيانات منها.

على سبيل المثال، تعد الأرقام 1.234 و 1234 أرقام صالحة، ولكن دون إنشاء ما تعنيه الرموز أنك لن تكون قادرا على معرفة ما هو عليه.

شخصيا، أود أن أكتب وظيفة حاول القيام ب "أفضل تخمين" بناء على بعض القواعد ...

  • إذا كان الرقم يحتوي , قبل ., ، ومن بعد , يجب أن يكون لآلاف و . يجب أن يكون لعشري
  • إذا كان الرقم يحتوي . قبل ,, ، ومن بعد . يجب أن يكون لآلاف و , يجب أن يكون لعشري
  • إذا كان هناك> 1 , الرموز، يجب أن يكون الفاصل ألف ,
  • إذا كان هناك> 1 . الرموز، يجب أن يكون الفاصل ألف .
  • إذا كان هناك 1 فقط , كم عدد الأرقام المتابعة؟ إذا لم يكن 3، فيجب أن يكون الفاصل العشري (نفس القاعدة ل .)
  • إذا كانت هناك 3 أرقام تفصل بها (على سبيل المثال 1،234 و 1.234)، فربما يمكنك وضع هذا الرقم جانبا وتحليل الأرقام الأخرى في نفس الصفحة لمحاولة معرفة ما إذا كانت تستخدم فواصل مختلفة، ثم تعود إليها؟

بمجرد اكتشاف منفصلة العشرية، قم بإزالة أي آلاف فواصل (غير مطلوبة لتحليل الرقم) وضمان الفصل العشري هو. في السلسلة التي تحلها. ثم يمكنك تمرير هذا Double.TryParse

سيتعين عليك إنشاء وظيفتك الخاصة لتخمين ما هو الفاصل العشري وفا فاصل. ثم سوف تكون قادرة على مضاعفة.اللورة ولكن مع الثقافة المقابلة.

أوصي بفعل شيء من هذا القبيل (فقط IE هذه ليست وظيفة اختبار الإنتاج):

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) حيث يمكن أن يكون فاصل عشري أو آلاف.

يمكنك حفظ هذا النظر في المستند للحصول على رقم مع مقدار البيانات اللازمة لمعرفة الساحرة هي ثقافة المستند، وحفظ تلك الثقافة واستخدامها دائما نفس الثقافة (إذا كنت تستطيع الأزياء أن المستند كلها في نفس الثقافة ...)

آمل أن يساعد هذا

يجب أن تكون قادرا على ذلك مع Double.TryParse. وبعد لديك أكبر مشكلة كما أراها هي أن لديك تناسق في الطريقة التي تفسر بها الأرقام.

على سبيل المثال، كيف يمكن

"-133.696" => -133696  

متي

"-166.713" => -166.713

?

إذا لم تكن قواعد تحويل الأرقام متسقة، فلن تتمكن من حل هذا في التعليمات البرمجية. كما أشار Klausbyskov، لماذا تتمتع الفترة الموجودة في "-133.696" بمعنى مختلف من واحد في "-166.713"؟ كيف تعرف ما الذي يجب القيام به مع رقم يحتوي على نقطة عشرية مع إعطاء هذه الأمثلة 2 حيث يستخدمه المرء كما هو متوقع ولكن الآخر يستخدمه كألف فاصل؟

ستحتاج إلى تحديد الحالات المختلفة التي من المحتمل أن تواجهها، وإنشاء بعض المنطق لتتناسب مع كل سلسلة واردة إلى إحدى الحالات الخاصة بك، ثم تحليلها تحديد FormessProvider مناسب. على سبيل المثال - إذا كانت السلسلة تحتوي على نقطة عشرية قبل فاصلة، فيمكنك افتراض أنه بالنسبة لهذه السلسلة المعينة، فإنها تستخدم النقطة العشرية كفاصل الآلاف والفاصلة كفاصل عشري، حتى تتمكن من إنشاء مزود تنسيق لمواجهة هذا السيناريو.

جرب شيئا على طول هذه الخطوط:

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، Getplepsformatprovider (MyString)) لإجراء التحليل الفعلي.

"ثم تقديم القيمة للمستخدم لتحديد ما إذا كان هذا صحيحا أم لا."

إذا كان هناك إمكانيات متعددة، فلماذا لا تظهر المستخدم كلاهما؟

يمكنك الحصول على طرق متعددة تدعو TryParse مع الثقافات المختلفة التي تريد أن تكون قادرا على التعامل معها، وجمع نتائج التحليل لتلك الأساليب التي تنجح في قائمة (إزالة التكرارات).

يمكنك حتى تقدير احتمالية عدم صحة الاحتمالات على أساس التردد الذي يتم استخدام التنسيقات المختلفة في مكان آخر في المستند، وتقديم البدائل في قائمة مرتبة حسب احتمال التصحيح. على سبيل المثال، إذا كنت قد شاهدت الكثير من الأرقام مثل 3،456،231.4 بالفعل، يمكنك تخمين أن الفاصلة على الأرجح للآلاف Seperator عندما ترى 4،675 لاحقا في نفس المستند، وتقديم "4675" أولا في القائمة، و "4.675" الثاني وبعد

إذا كان لديك نقطة أو فاصلة متبوعة بأكثر من رقمين، فهي النقطة العشرية. خلاف ذلك، تجاهله.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top