Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Operator Overloading for generic value class #524

Open
b-twis opened this issue Jan 13, 2021 · 3 comments
Open

Operator Overloading for generic value class #524

b-twis opened this issue Jan 13, 2021 · 3 comments

Comments

@b-twis
Copy link

b-twis commented Jan 13, 2021

Hi,

I have a scenario where I would like to use a custom class as the value of a Series. I have overloaded the operators on the custom class so they can nativly be used.

The base implementation of Series operator overloading only accepts some limited data types (int, float, decimal).

It would be great to allow a generic value class to be used, relying on the underlying operator overloading of that class.
Certain properties of the custom class should be modified (quantity & price) but others should be ignored.

var s1 = new SeriesBuilder<DateTime, DeliverySlot>
{
    { DateTime.Parse("2020-01-01 01:00"), new DeliverySlot(1.0M,1.0M) },
    { DateTime.Parse("2020-01-01 02:00"), new DeliverySlot(2.0M,2.0M) },
    { DateTime.Parse("2020-01-01 03:00"), new DeliverySlot(3.0M,3.0M) }
}.Series;

var s2 = new SeriesBuilder<DateTime, DeliverySlot>
{
    { DateTime.Parse("2020-01-01 01:00"), new DeliverySlot(1.0M,1.0M) },
    { DateTime.Parse("2020-01-01 02:00"), new DeliverySlot(2.0M,2.0M) },
    { DateTime.Parse("2020-01-01 03:00"), new DeliverySlot(3.0M,3.0M) }
}.Series;

// Error: Operator '+' cannot be applied to the operands of type 'Series<DateTime,DeliverySlot>' and 'Series<DateTime,DeliverySlot>' 

var sSum = s1 + s2; 

public readonly struct DeliverySlot : IEquatable<DeliverySlot>, ICloneable
{
    private readonly decimal _quantity;
    private readonly decimal _price;


    public DeliverySlot(decimal quantity, decimal price)
    {
        _quantity = quantity;
        _price = price;
    }

    public static DeliverySlot operator +(DeliverySlot a) => a;
    public static DeliverySlot operator -(DeliverySlot a) => new DeliverySlot(-a._quantity, -a._price);

    public static DeliverySlot operator +(DeliverySlot a, DeliverySlot b)
        => new DeliverySlot(a._quantity + b._quantity, Math.Max(a._price, b._price));
    public static DeliverySlot operator +(DeliverySlot a, decimal b)
       => new DeliverySlot(a._quantity + b, a._price);

    public static DeliverySlot operator -(DeliverySlot a, DeliverySlot b)
        => a + (-b);

    public static DeliverySlot operator *(DeliverySlot a, DeliverySlot b)
        => new DeliverySlot(a._quantity * b._quantity, Math.Max(a._price, b._price));

    public override bool Equals(object obj)
    {
        return (obj is DeliverySlot) && Equals(obj);
    }

    public bool Equals(DeliverySlot other)
    {
        return this.GetHashCode() == other.GetHashCode();
    }

    public override int GetHashCode()
    {
        int iHash = 0;
        iHash ^= (23 * 37) + _quantity.GetHashCode();
        return iHash;
    }
    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

Is this something that can be added to the core Deedle solution?

Alternatively, I was looking at creating a new class whcih extends the Series<K,V> allowing me to create my own operator overloads.

I am primarily a c# dev, and the implementation of the overloading in f# is not familiar to me.

// Operators and F# functions

Could someone assist on this approach/explaining the steps in the VectorOperation and ScalarOperationR methods?

Thanks,

Basil

@zyzhu
Copy link
Contributor

zyzhu commented Feb 21, 2021

I am not sure the first option to create a generic class is doable. In your case, you overloaded the operators on price and quantity properties. But there are infinite scenarios of classes. Users of Deedle library shall not expect to inherit a base class from Deedle so as to get the operator overloaded. The operators are supposed to be overridden for each scenario.

As for the second part, I'm afraid I cannot offer any help. I barely write C# code.

@b-twis
Copy link
Author

b-twis commented Feb 23, 2021

HI zyzhu,

Thank you for the response. I understand there are infinite scenarios when implementing generics, but thought I would ask.

I dont need the c# code as such, more an explanation of the F# code so I could attempt to reimplement it in c#.

static member inline internal VectorOperation<'T>(series1:Series<'K,'T>, series2:Series<'K,'T>, op): Series<_, 'T> =
   let newIndex, lcmd, rcmd =
     createJoinTransformation series1.IndexBuilder series2.IndexBuilder
       JoinKind.Outer Lookup.Exact series1.Index series2.Index (Vectors.Return 0) (Vectors.Return 1)
   let vecRes = Vectors.Combine(lazy newIndex.KeyCount, [lcmd; rcmd], BinaryTransform.CreateLifted<'T>(op))
   let vector = series1.VectorBuilder.Build<'T>(newIndex.AddressingScheme, vecRes, [| series1.Vector; series2.Vector |])
   Series(newIndex, vector, series1.VectorBuilder, series1.IndexBuilder)

I prototyped something like the following, which kind of got me what I was looking for however I never got to finishing the Vector Operation class or implementing the Scalar Operations into DeliverySeries.

Just leaving it here in case someone has thoughts, or is looking for something similar.

public class DeliverySeries : Series<DateTimeDST, OptionalValue<DeliverySlot>>
    {

        public DeliverySeries(IEnumerable<KeyValuePair<DateTimeDST, OptionalValue<DeliverySlot>>> pairs) : base(pairs)
        {
        }

        public DeliverySeries(DateTimeDST[] keys, OptionalValue<DeliverySlot>[] values) : base(keys, values)
        {
        }
        public DeliverySeries(IEnumerable<DateTimeDST> keys, IEnumerable<OptionalValue<DeliverySlot>> values) : base(keys, values)
        {
        }

        public static DeliverySeries operator +(DeliverySeries a) => a;
        public static DeliverySeries operator -(DeliverySeries a) => new DeliverySeries(a.Keys, a.Values.Select(x => new OptionalValue<DeliverySlot>(-x.Value)));


        private static DeliverySeries VectorOperation(DeliverySeries a, DeliverySeries b, string op)
        {
            var unionKeys = a.Keys.Union(b.Keys).Distinct().OrderBy(x => x).ToArray();
            OptionalValue<DeliverySlot>[] cValues = new OptionalValue<DeliverySlot>[unionKeys.Count()];

            for (int i = 0; i < unionKeys.Length; i++)
            {
                if (a.ContainsKey(unionKeys[i]) && b.ContainsKey(unionKeys[i]))
                {
                    var aValue = a.Get(unionKeys[i]);
                    var bValue = b.Get(unionKeys[i]);
                    switch (op)
                    {
                        case "+":
                            cValues[i] = new OptionalValue<DeliverySlot>(aValue.Value + bValue.Value);
                            break;
                        case "-":
                            cValues[i] = new OptionalValue<DeliverySlot>(aValue.Value - bValue.Value);
                            break;
                        case "*":
                            cValues[i] = new OptionalValue<DeliverySlot>(aValue.Value * bValue.Value);
                            break;
                        case "/":
                            cValues[i] = new OptionalValue<DeliverySlot>(aValue.Value / bValue.Value);
                            break;
                    }
                }
                else
                {
                    cValues[i] = new OptionalValue<DeliverySlot>();
                }
            }

            return new DeliverySeries(unionKeys, cValues);
        }

        public static DeliverySeries operator +(DeliverySeries a, DeliverySeries b)
        {
            return VectorOperation(a, b, "+");
        }

        public static DeliverySeries operator -(DeliverySeries a, DeliverySeries b)
        {
            return VectorOperation(a, b, "-");
        }

        public static DeliverySeries operator *(DeliverySeries a, DeliverySeries b)
        {
            return VectorOperation(a, b, "*");
        }

        public static DeliverySeries operator /(DeliverySeries a, DeliverySeries b)
        {
            return VectorOperation(a, b, "/");
        }
    }

    public readonly struct DeliverySlot : IEquatable<DeliverySlot>, ICloneable
    {
        private readonly decimal _quantity;
        private readonly decimal _price;

        public DeliverySlot(decimal quantity, decimal price)
        {
            _quantity = quantity;
            _price = price;
        }

        public static DeliverySlot operator +(DeliverySlot a) => a;

        public static DeliverySlot operator -(DeliverySlot a) => new DeliverySlot(-a._quantity, a._price);

        //series,series
        public static DeliverySlot operator +(DeliverySlot a, DeliverySlot b)
            => new DeliverySlot(a._quantity + b._quantity, Math.Max(a._price, b._price));

        public static DeliverySlot operator -(DeliverySlot a, DeliverySlot b)
            => new DeliverySlot(a._quantity - b._quantity, a._price);

        public static DeliverySlot operator *(DeliverySlot a, DeliverySlot b)
            => new DeliverySlot(a._quantity * b._quantity, Math.Max(a._price, b._price));

        public static DeliverySlot operator /(DeliverySlot a, DeliverySlot b)
            => new DeliverySlot(a._quantity / b._quantity, Math.Max(a._price, b._price));

        // series, decimal
        public static DeliverySlot operator +(DeliverySlot a, decimal b)
            => new DeliverySlot(a._quantity + b, a._price);
        public static DeliverySlot operator -(DeliverySlot a, decimal b)
            => new DeliverySlot(a._quantity - b, a._price);

        public override bool Equals(object obj)
        {
            return (obj is DeliverySlot) && Equals(obj);
        }

        public bool Equals(DeliverySlot other)
        {
            return this.GetHashCode() == other.GetHashCode();
        }

        public override int GetHashCode()
        {
            int iHash = 0;
            iHash ^= (23 * 37) + _quantity.GetHashCode();
            return iHash;
        }

        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }

    public class DateTimeDST : IEquatable<DateTimeDST>, IComparable<DateTimeDST>, ICloneable
    {
        private readonly DateTime _datetime;
        public DateTime DateTime
        {
            get
            {
                return _datetime;
            }
        }

        private readonly bool _dst;

        public bool DST
        {
            get
            {
                return _dst;
            }
        }


        public DateTimeDST(DateTime dateTime)
        {
            _datetime = dateTime;
            _dst = false;
        }
        public DateTimeDST(string dateTime)
        {
            _datetime = DateTime.Parse(dateTime);
            _dst = false;
        }

        public DateTimeDST(DateTime dateTime, bool isDST)
        {
            _datetime = dateTime;
            _dst = isDST;
        }

        public DateTimeDST(string dateTime, bool isDST)
        {
            _datetime = DateTime.Parse(dateTime);
            _dst = isDST;
        }

        public static explicit operator DateTime(DateTimeDST thisDateTimeDST)
        {
            return thisDateTimeDST._datetime;
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as DateTimeDST);
        }

        public bool Equals(DateTimeDST other)
        {
            return this.GetHashCode() == other?.GetHashCode();
        }

        public override int GetHashCode()
        {
            int iHash = 0;
            iHash ^= (23 * 37) + _datetime.GetHashCode();
            iHash ^= (23 * 37) + _dst.GetHashCode();
            return iHash;
        }

        public int CompareTo(DateTimeDST compareDate)
        {
            if (compareDate is null)
                throw new ArgumentNullException(nameof(compareDate));

            int result = DateTime.CompareTo(compareDate.DateTime);
            if (result == 0)
                result = DST.CompareTo(compareDate.DST);
            return result;
        }


        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }

@zyzhu
Copy link
Contributor

zyzhu commented Feb 24, 2021

As you already overloaded the operators on DeliveryShot, you may use Zip function to zip two series together and apply your overloaded operators such as the following.

var s1 = new SeriesBuilder<DateTime, DeliverySlot>
                    {
                        { DateTime.Parse("2020-01-01 01:00"), new DeliverySlot(1.0M, 1.0M) },
                        { DateTime.Parse("2020-01-01 02:00"), new DeliverySlot(2.0M, 2.0M) },
                        { DateTime.Parse("2020-01-01 03:00"), new DeliverySlot(3.0M, 3.0M) }
                    }.Series;
var s2 = new SeriesBuilder<DateTime, DeliverySlot>
                    {
                        { DateTime.Parse("2020-01-01 01:00"), new DeliverySlot(1.0M, 1.0M) },
                        { DateTime.Parse("2020-01-01 02:00"), new DeliverySlot(2.0M, 2.0M) },
                        { DateTime.Parse("2020-01-01 03:00"), new DeliverySlot(3.0M, 3.0M) }
                    }.Series;
var s3 = s1.Zip(s2).Select(kvp => kvp.Value.Item1.Value + kvp.Value.Item2.Value);

The above snippet doesn't check missing values. Make sure you check whether each value HasValue before running operators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants