1

I have read about the IEqualityComparer interface. Here is my code (which says more then a thousand words)

static void Main(string[] args)
{
    var Send = new ObservableCollection<ProdRow>() {
        new ProdRow() { Code = "8718607000065", Quantity = 1 },
        new ProdRow() { Code = "8718607000911", Quantity = 10 }
    };
    var WouldSend = new ObservableCollection<ProdRow>() {
        new ProdRow() { Code = "8718607000065", Quantity = 1 },
        new ProdRow() { Code = "8718607000072", Quantity = 1 },
        new ProdRow() { Code = "8718607000256", Quantity = 1 },
        new ProdRow() { Code = "8718607000485", Quantity = 1 },
        new ProdRow() { Code = "8718607000737", Quantity = 1 },
        new ProdRow() { Code = "8718607000911", Quantity = 20 }
    };

    //var sendToMuch = Send.Except(WouldSend).ToList();
    //var sendToLittle = WouldSend.Except(Send).ToList();

    //if (sendToMuch.Any() || sendToLittle.Any())
    //    var notGood = true;
    //else
    //    var okay = true;

    var sendToMuch = Send.ToList();
    var sendToLittle = WouldSend.ToList();

    foreach (var s in Send) {
        var w = WouldSend.FirstOrDefault(d => d.Code.Equals(s.Code));

        if (w != null) {
            if (w.Quantity == s.Quantity) {
                sendToMuch.Remove(s);
                sendToLittle.Remove(w);
                continue;
            }
            if (w.Quantity > s.Quantity) {
                sendToLittle.Single(l => l.Code == w.Code).Quantity = (w.Quantity - s.Quantity);
                sendToMuch.Remove(s);
            } else {
                sendToMuch.Single(l => l.Code == w.Code).Quantity = (s.Quantity - w.Quantity);
                sendToLittle.Remove(s);
            }
        } else {
            sendToMuch.Add(s);
        }
    }
}

The commented lines where what I would hoped that would work... the stuff below with what I ended up with.

As reference, here is my ProdRow class:

class ProdRow : INotifyPropertyChanged, IEqualityComparer<ProdRow>
{
    private string _code;
    private int _quantity;
    public string Code {
        get { return _code; }
        set {
            _code = value;
            OnPropertyChanged("Code");
        }
    }
    public int Quantity {
        get { return _quantity; }
        set {
            _quantity = value;
            OnPropertyChanged("Quantity");
        }
    }

    private void OnPropertyChanged(string v) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
    }

    public new bool Equals(object x, object y) {
        if (((ProdRow)x).Code.Equals(((ProdRow)y).Code) && ((ProdRow)x).Quantity == ((ProdRow)y).Quantity)
            return true;
        else
            return false;
    }
    public int GetHashCode(object obj) {
        return obj.GetHashCode();
    }
    public bool Equals(ProdRow x, ProdRow y) {
        if (x.Code.Equals(y.Code) && x.Quantity == y.Quantity)
            return true;
        else
            return false;
    }
    public int GetHashCode(ProdRow obj) {
        throw new NotImplementedException();
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

I did not expected the commented part to work, because it cannot know to decrease the int of quantity etc. but I would like to know if there is a more efficient way to do this then the solution I used (below the commented lines). Perhaps flatten the collection like a string[]?

P.S. Sorry for the "PascalCase" of Send and WouldSend

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
JP Hellemons
  • 5,977
  • 11
  • 63
  • 128
  • 1
    What exactly do you want to do ? You have this 2 collection, what do you want to be result ? – mybirthname Dec 20 '16 at 13:51
  • for instance `sendToLittle` should have a prodrow with code "8718607000911" and quantity 10 and `sendToMuch` should be empty. i.e. the output of the code which is not commented. – JP Hellemons Dec 20 '16 at 14:03
  • So, as I understand you want something like a (outer) join on `Code` with a Diff (substraction) on `Quantity` and a filter on combined quantity not equal zero? – grek40 Dec 20 '16 at 14:06
  • @grek40 desired output as in the code. Two lists/collections: one with code and quantity which has been send too much and one with code and quantity which has not been send enough. so the first product which has been send is perfect, the second one should have 10 more items to be perfect so is not send enough. then there are other products completely missing and should have been send. – JP Hellemons Dec 20 '16 at 14:13
  • Yea, so what. If you follow the approach I mentioned, you can separate the "ToMuch" and "NotEnough" quantities by separating results between a positive and negative diff value. For full outer join, have a look at http://stackoverflow.com/a/13503860/5265292 maybe I find time to write an answer later, but not now. – grek40 Dec 20 '16 at 14:16
  • Thanks @grek40 for the url. Full Outer with diff seems to be what I am looking for. – JP Hellemons Dec 20 '16 at 14:23

1 Answers1

3

IEqualityComparer<T> is not the right interface to implement for a class whose instances you wish to compare. IEqualityComparer<T> implementations are for creating objects that do comparisons from the outside of the objects being compared, which becomes important when you need to re-define what it means for two objects to be equal without access to the code of these objects, or when you need to use different semantic for equality depending on the context.

The right interface for strongly typed equality comparison is IEquatable<T>. However, in your case all you need is overriding Object's Equals(object) and GetHashCode():

public new bool Equals(object obj) {
    if (obj == this) return true;
    var other = obj as ProdRow;
    if (other == null) return false;
    return Code.Equals(other.Code) && Quantity == other.Quantity;
}
public int GetHashCode() {
    return 31*Code.GetHashCode() + Quantity;
}

As far as computing quantities goes, you can do it with negative numbers and GroupBy:

var quantityByCode = WouldSend.Select(p => new {p.Code, p.Quantity})
    .Concat(Send.Select(p => new {p.Code, Quantity = -p.Quantity}))
    .GroupBy(p => p.Code)
    .ToDictionary(g => g.Key, g => g.Sum(p => p.Quantity));
var tooLittle = quantityByCode
    .Where(p => p.Value > 0)
    .Select(p => new ProdRow {Code = p.Key, Quantity = p.Value})
    .ToList();
var tooMuch = quantityByCode
    .Where(p => p.Value < 0)
    .Select(p => new ProdRow {Code = p.Key, Quantity = -p.Value})
    .ToList();
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thanks that interface does work better and see's the first product perfectly. But for the second one, it should reduce the quantity. as said, perhaps I need to flatten the collection first in order to get two collections of items which has been send too much or too few. – JP Hellemons Dec 20 '16 at 14:15
  • Really like your Linq addition! Thanks! only thing is that the naming of tooMuch and tooLittle should be switched :) – JP Hellemons Dec 21 '16 at 12:43
  • @JPHellemons Done. Thanks! – Sergey Kalinichenko Dec 21 '16 at 13:11