سؤال

هل هناك أي طريقة ممكنة لاستخدام الأدوية العامة لإنشاء مكتبة رياضيات لا تعتمد على النوع الأساسي المختار لتخزين البيانات؟

بمعنى آخر، لنفترض أنني أريد أن أكتب فئة الكسر.يمكن تمثيل الكسر باثنين من ints أو اثنين من doubles أو غير ذلك.الشيء المهم هو أن العمليات الحسابية الأربع الأساسية محددة جيدًا.لذا، أود أن أكون قادرًا على الكتابة Fraction<int> frac = new Fraction<int>(1,2) و/أو Fraction<double> frac = new Fraction<double>(0.1, 1.0).

للأسف لا توجد واجهة تمثل العمليات الأربع الأساسية (+،-،*،/).هل وجد أي شخص طريقة عملية وممكنة لتنفيذ ذلك؟

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

المحلول

فيما يلي طريقة لتجريد المشغلين وهي غير مؤلمة نسبيًا.

    abstract class MathProvider<T>
    {
        public abstract T Divide(T a, T b);
        public abstract T Multiply(T a, T b);
        public abstract T Add(T a, T b);
        public abstract T Negate(T a);
        public virtual T Subtract(T a, T b)
        {
            return Add(a, Negate(b));
        }
    }

    class DoubleMathProvider : MathProvider<double>
    {
        public override double Divide(double a, double b)
        {
            return a / b;
        }

        public override double Multiply(double a, double b)
        {
            return a * b;
        }

        public override double Add(double a, double b)
        {
            return a + b;
        }

        public override double Negate(double a)
        {
            return -a;
        }
    }

    class IntMathProvider : MathProvider<int>
    {
        public override int Divide(int a, int b)
        {
            return a / b;
        }

        public override int Multiply(int a, int b)
        {
            return a * b;
        }

        public override int Add(int a, int b)
        {
            return a + b;
        }

        public override int Negate(int a)
        {
            return -a;
        }
    }

    class Fraction<T>
    {
        static MathProvider<T> _math;
        // Notice this is a type constructor.  It gets run the first time a
        // variable of a specific type is declared for use.
        // Having _math static reduces overhead.
        static Fraction()
        {
            // This part of the code might be cleaner by once
            // using reflection and finding all the implementors of
            // MathProvider and assigning the instance by the one that
            // matches T.
            if (typeof(T) == typeof(double))
                _math = new DoubleMathProvider() as MathProvider<T>;
            else if (typeof(T) == typeof(int))
                _math = new IntMathProvider() as MathProvider<T>;
            // ... assign other options here.

            if (_math == null)
                throw new InvalidOperationException(
                    "Type " + typeof(T).ToString() + " is not supported by Fraction.");
        }

        // Immutable impementations are better.
        public T Numerator { get; private set; }
        public T Denominator { get; private set; }

        public Fraction(T numerator, T denominator)
        {
            // We would want this to be reduced to simpilest terms.
            // For that we would need GCD, abs, and remainder operations
            // defined for each math provider.
            Numerator = numerator;
            Denominator = denominator;
        }

        public static Fraction<T> operator +(Fraction<T> a, Fraction<T> b)
        {
            return new Fraction<T>(
                _math.Add(
                  _math.Multiply(a.Numerator, b.Denominator),
                  _math.Multiply(b.Numerator, a.Denominator)),
                _math.Multiply(a.Denominator, b.Denominator));
        }

        public static Fraction<T> operator -(Fraction<T> a, Fraction<T> b)
        {
            return new Fraction<T>(
                _math.Subtract(
                  _math.Multiply(a.Numerator, b.Denominator),
                  _math.Multiply(b.Numerator, a.Denominator)),
                _math.Multiply(a.Denominator, b.Denominator));
        }

        public static Fraction<T> operator /(Fraction<T> a, Fraction<T> b)
        {
            return new Fraction<T>(
                _math.Multiply(a.Numerator, b.Denominator),
                _math.Multiply(a.Denominator, b.Numerator));
        }

        // ... other operators would follow.
    }

إذا فشلت في تنفيذ النوع الذي تستخدمه، فسوف تحصل على فشل في وقت التشغيل بدلاً من وقت الترجمة (وهذا أمر سيء).تعريف MathProvider<T> ستكون عمليات التنفيذ دائمًا هي نفسها (سيئة أيضًا).أود أن أقترح عليك تجنب القيام بذلك في C# واستخدام F# أو أي لغة أخرى أكثر ملاءمة لهذا المستوى من التجريد.

يحرر: تعريفات ثابتة للجمع والطرح ل Fraction<T>.شيء آخر مثير للاهتمام وبسيط هو تنفيذ MathProvider الذي يعمل على شجرة بناء جملة مجردة.تشير هذه الفكرة على الفور إلى القيام بأشياء مثل التمايز التلقائي: http://conal.net/papers/beautiful-differentiation/

نصائح أخرى

أعتقد أن هذا يجيب على سؤالك:

http://www.codeproject.com/KB/cs/genericnumerics.aspx

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

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

أولاً، يجب على صفك أن يقصر المعلمة العامة على الأوليات ( public class Fraction حيث T :الهيكل الجديد ()).

ثانيًا، ربما ستحتاج إلى إنشاء التحميل الزائد الضمني حتى تتمكن من التعامل مع النقل من نوع إلى آخر دون بكاء المترجم.

ثالثًا، يمكنك أيضًا زيادة التحميل على العوامل الأساسية الأربعة لجعل الواجهة أكثر مرونة عند دمج الكسور من أنواع مختلفة.

أخيرًا، عليك أن تفكر في كيفية تعاملك مع العمليات الحسابية والتدفقات السفلية.ستكون المكتبة الجيدة واضحة للغاية في كيفية تعاملها مع التجاوزات؛وإلا فلا يمكنك الوثوق بنتائج العمليات لأنواع مختلفة من الكسور.

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