0

I have a List<Meb> (a bar nesting), each of these nestings have a list of details inside.
All of these bars are unique, because each of element inside is unique by its ID.

Now I want to add a checkbox, in order to group or not all bars that have the same list of details inside (the list of items inside are identical, except their ID, and some parameters I first set to -1 or ""). Here is the function I made in order to do that :

private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
    List<Meb> retour = new List<Meb>();
    foreach(Meb mebOri in mebInput)
    {
        Meb meb = new Meb();
        meb.ID = -1;
        meb.Number = mebOri.Number;
        meb.Length = mebOri.Length;
        meb.Quantity=mebOri.Quantity;
        foreach(Repere repOri in mebOri.ListReperes)
        {
            Repere rep = new Repere();
            rep.Name = repOri.Name;
            rep.Quantite = repOri.Quantite;
            rep.ID = -1;
            meb.ListReperes.Add(rep);
        }
        retour.Add(meb);


    }
    retour = retour.GroupBy(l => l.ListReperes)
            .Select(cl => new Meb
            {
                ID=-1,
                Number = cl.First().Number,
                Length = cl.First().Length,
                Quantity=cl.Sum(c => c.Quantity),
                ListReperes = cl.First().ListReperes,
            }).ToList();
    return retour;
}

The idea is that:

1st: I create a new List<Meb> that copies the original List<Meb>, for the List<Repere>, I also copy it, but setting the ID to "-1", as others properties that could differ between them.

2nd: I make a group by on the List<Repere>

But on the end no groupby is done, and the output remains the same as the input.

Edit :

I explain better the structure of my objects because it seems it was not clear enough :

Each Meb object represents a beam, each beams contains Repere objects(details), these details have a lot of parameters, most importants are ID, Name, Quantity, concrete example :

                           ID    Name        Quantity

Meb1(Quantity1) contains : 11    Repere1     2
                           20    Repere2     1
                           25    Repere3     1

Meb2(Quantity2) contains : 12    Repere1     2
                           24    Repere2     2
                           28    Repere3     1

Meb3(Quantity3) contains : 31    Repere1     2
                           18    Repere2     1
                           55    Repere3     1

So I import my List<Meb>, and I want to group all my Mebs, comparing their details list.
In that case the result would be :

Meb1(Quantity4) contains : 0    Repere1     2
                           0    Repere2     1
                           0    Repere3     1

Meb2(Quantity2) contains : 0    Repere1     2
                           0    Repere2     2
                           0    Repere3     1
Siegfried.V
  • 1,508
  • 1
  • 16
  • 34
  • I would recommend trying to make the example much simplier. It is hard to follow what you are trying to actually achieve. It looks to me like you are attempting to do a GroupBy -- on an array -- which is not how GroupBy is done. – Ilan Keshet Jun 16 '18 at 21:18
  • ok made it thanks. In fact, I try to make a groupby -- on a list of objects -- my list has a list of , each Meb has a List, if the list are identical, so I group. First time I try to make something like that, and not sure it is possible to do? – Siegfried.V Jun 16 '18 at 21:25

3 Answers3

2

I would recommend that you add some sort of property in your Meb class that hashes all of your ListReperes items, and then group off that.

You can have a look at this link: How to generate a unique hash for a collection of objects independent of their order

IE then you would do: retour = retour.GroupBy(l => l.HashReperes) and this would provide you a unique grouped list of your lists.

where HashReperes is the property that provides the Hash of the Reperes List.

Ilan Keshet
  • 514
  • 5
  • 19
  • ok, so if I understand your answer, I cannot group a list by a list?Aftr reading a bit on internet,HashCode seems a good solution to me, but I see that even if lists are identical, they generate a different hashcode. What I did is foreach(Meb meb in listeMeb) meb.ID= meb.ListReperes.GetHashCode(); did I understand it correct? – Siegfried.V Jun 17 '18 at 05:09
  • As HashCode didn't work as I expected, I made my own function : I added some string property where I concatenate each element of ListReperes (I concatenate only parameters that I want to compare, as name and quantity). So after I have the same string for all of them. But when grouping I meet a new error that objects may be IComparable(I think it joins jdweng answer). But why do they need to be IComparable? I already use groupby on list of objects that are not IComparable? Or is this because this time I also have lists inside? – Siegfried.V Jun 17 '18 at 06:03
  • Meb is not a List object it is a class. The comparison needs to know the you are only comparing number and length; and not ID and quantity. – jdweng Jun 17 '18 at 10:46
  • jdweng maybe I think you didn't understand the question(or I didn't explain well enough), I want to compare not number and length, but a List. In my case, I want to group a List, each Meb object contains a List, and that's that List I want to compare. On the end I could not use your answer(compare the lists), so I used Ilan Keshet's tip(explained in my previous comment). I will put it as an edit of my post(or as an answer if U can please confirm it is correct to do so). – Siegfried.V Jun 17 '18 at 15:25
2

Use IEquatable. Then you can use the standard linq GroupBy(). See code below

    public class Meb : IEquatable<Meb>, INotifyPropertyChanged
    {
        public int ID { get; set; }
        public int Number { get; set; }
        public int Length { get; set; }
        public int Quantity { get; set;}

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
        {

            return mebInput.GroupBy(x => x).Select(x => new Meb() {
                ID = x.First().ID,
                Number = x.First().Number,
                Length = x.First().Length,
                Quantity = x.Sum(y => y.Quantity)
            }).ToList();

        }

        public bool Equals(Meb other)
        {
            if ((this.Number == other.Number) && (this.Length == other.Length) && (this.Quantity == other.Quantity))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        public override int GetHashCode()
        {
            return ID;
        }
    }

If you don't want to use IEquatable then use this

       private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
        {

            return mebInput.GroupBy(x => new { number = x.Number, len = x.Length }).Select(x => new Meb()
            {
                ID = x.First().ID,
                Number = x.Key.number,
                Length = x.Key.len,
                Quantity = x.Sum(y => y.Quantity)
            }).ToList();

        }

For comparing a List use something like this

    public class MyClassA : IEquatable<List<MyClassB>>
    {
        public List<MyClassB> myClassB { get; set; }

        public bool Equals(List<MyClassB> other)
        {
            if(other == null) return false;
            if (this.myClassB.Count() != other.Count()) return false;

            var groupThis = this.myClassB.OrderBy(x => x.propertyA).ThenBy(x => x.propertyB).GroupBy(x => x).ToList();
            var groupOther = other.OrderBy(x => x.propertyA).ThenBy(x => x.propertyB).GroupBy(x => x).ToList();

            if (groupThis.Count() != groupOther.Count) return false;

            for (int i = 0; i < groupThis.Count(); i++)
            {
                if (groupThis[i].Count() != groupOther[i].Count()) return false;
            }
            return true;
        }
        public override int GetHashCode()
        {
            return 0;
        }
    }
    public class MyClassB : IEquatable<MyClassB>
    {
        public int propertyA { get; set; }
        public string propertyB { get; set; }

        public bool Equals(MyClassB other)
        {
            if (other == null) return false;

            if ((this.propertyA == other.propertyA) && (this.propertyB == other.propertyB))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        public override int GetHashCode()
        {
            return 0;
        }

    }
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • Also tried your proposal, but my class is already INotifyPropertyChanged, and if I set it as public class Meb : INotifyPropertyChanged IEquatable, I have a syntax error (what would be correct syntax then?) – Siegfried.V Jun 17 '18 at 05:59
  • 1
    You are probably just missing a comma. – jdweng Jun 17 '18 at 10:42
  • Now I am getting an error "At least one IComparable interface must be implemented in one object."(translate from Russian, so I don't know if correct. Regarding the Equals function, it is automatically called when grouping? Finally the GetHashCode, I don't catch how it is working. Also noticed the main difference between my code and yours, is that I compare a List, when I don't have any list inside the object, groupby is working correctly, only meet problem wen the objects contain a List – Siegfried.V Jun 17 '18 at 13:08
  • The hash code is really doesn't do much in this example. You can just return zero and it will work. The linq automatically calls the class Equals method for GroupBy. You can add a test for null in the Equals Method. See msdn example : https://msdn.microsoft.com/en-us/library/ms131190(v=vs.110).aspx. Look at msdn example to see if it give a clue about the error. I think you are missing a "using" at the top of your code. I will post a way of doing same without IEquatable. – jdweng Jun 17 '18 at 13:34
  • ok I just understood that Equals function is used when I do GroupBy(l => l). I may have missed something, because I believed the Equals function would be called by groupby, then I see that first the groupby is done(without grouping anything), and only after "return retour", the Equals is called x times, and with "other" always empty(other.Count=0). Just saw your edit, will try this then, thanks – Siegfried.V Jun 17 '18 at 13:49
  • Is this possible that the problem is I am trying to compare some List, and not just some double or int as in your example? Because as I said, usualy I meet no problem to make something as your example without IEquatable, but in that case I cannot (I also tried to set the list as an empty list, and even like that nothing is grouped). – Siegfried.V Jun 17 '18 at 14:15
  • String and integers (as well as other standard types) have a built in Equal Method so you do not have any issues with comparing. Your class has ID, Number, Length, and Quantity. you only want to check the Number and Length Properties. Using "new { number = x.Number, len = x.Length }" will solve the issue. This is a simple case but in more complicated cases you probably want to implement a custom IEquatable. – jdweng Jun 17 '18 at 15:43
  • read again my post please, I don't want to check the number and length properties, but the ListReperes property, that is a List in my Meb description. in summary, I can easily check a number or a length(string, int, long), but not a List. I will put the answer I found in a moment. Also will be appreciated to have your opinion about it. – Siegfried.V Jun 17 '18 at 15:49
  • What is Repere? Maybe you want the class ? Repere to be IEquatable. – jdweng Jun 17 '18 at 16:15
  • wow... learned 2 new things... IEquatable, and also didn't know it was possible to make a class of List... Is there a big gain of performance using IEquatable? Because my method(below) works good, and honestly I don't manage to use your last code. I could implement your Equals and List in the class(also implemented in class Meb), but on the end grouping is not working... I also edit the post in order to explain better what I want, because not sure it was understood – Siegfried.V Jun 18 '18 at 04:34
0

On the end, here is the way I could solve the problem :

private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
    List<Meb> retour = new List<Meb>();
    foreach(Meb mebOri in mebInput)
    {
        Meb meb = new Meb();
        meb.ID = -1;
        meb.Number = mebOri.Number;
        meb.Length = mebOri.Length;
        meb.Quantity=mebOri.Quantity;
        foreach(Repere repOri in mebOri.ListReperes)
        {
            Repere rep = new Repere();
            rep.Name = repOri.Name;
            rep.Quantite = repOri.Quantite;
            rep.ID = -1;
            meb.ListReperes.Add(rep);
        }
        retour.Add(meb);
        // Here I added a string property, in which I concatenate 
        //name and quantity of each Repere in my List<Repere>, 
        //so on the end the "SomeString" parameters will be identical
        //for all Meb that have the same List<Repere> (ignoring the IDs).
        foreach(Meb meb in retour)
        {
            meb.SomeString = "";
            foreach(RepereNest rep in meb.ListReperes)
            {
                meb.SomeString += rep.Name + rep.Quantite;
            }
        }


    }
    retour = retour.GroupBy(l => l.SomeString)
            .Select(cl => new Meb
            {
                ID=-1,
                Number = cl.First().Number,
                SomeString=cl.First().SomeString,
                Length = cl.First().Length,
                Quantity=cl.Sum(c => c.Quantity),
                ListReperes = cl.First().ListReperes,
            }).ToList();
    return retour;
}

Well for now ths is the only way I could find to group not on my parameters(for this no problem), but on parameters inside a List of my object. And I think this method is not so bad, because I also have Lists inside of Repere objects, so I could use the same tip in future. On the end I just don't understand why it is not possible to check when Lists of my objects are equals?

Siegfried.V
  • 1,508
  • 1
  • 16
  • 34