tags:

views:

109

answers:

4

Here is the pseudo case:

class parent{
   string name; //Some Property
   List<int> myValues;

   .......
}
........
//Initialize some parent classes

List<parent> parentList = new List<parent>();
parentList.add(parent123); //parent123.myValues == {1,2,3}
parentList.add(parent456); //parent456.myValues == {4,5,6}
parentList.add(parentMatch); //parentMatch.myValues == {1,2,3}

What I am aiming for is a query which retrieves a List of parent objects where their myValues Lists are equivalent. In this case it would return parent123 and parentMatch.

+1  A: 

Very silly solution:

var groups = list.GroupBy(p => string.Join(",", p.list.Select(i => i.ToString()).ToArray()))
                    .Where(x => x.Count() > 1).ToList();

Result:

an IEnumerable of groups containing parent objects having list with same int (in the same order).

If you need to match list of elements in any order (i.e. 1,2,3 == 3,1,2), just change p.list to p.list.OrderBy(x => x).

Plus, if you're targeting framework 4.0, you can avoid ToArray and ToString


EDIT:

added a where to filter single-occurrence groups.

Now if you have these parents:

parent  A  1,2,3
parent  B  1,2,3
parent  C  1,2,3
parent  D  4,5,6
parent  E  4,5,6
parent  F  7,8,9

it returns:

(A,B,C) - (D,E)
digEmAll
I like the simplicity
Zachary Yates
This returns the flattened array of ints, some work still then to find the first object whose list matches this flattened value. Also returns those with just 1 entry, not multiples.
p.campbell
It works fine, I've tested it. It returns groups of parents with same elements.
digEmAll
+1 @digEmAll: I haven't the time to change my solution to meet the OP's requirements; it will need more work to make it efficient. This solution is simple; and despite relying on *string* comparisons, it works and that's what's important.
Ani
@Ani: Yes, I was just modifying my solution to use IEqualityComparer to avoid string conversion/comparison, but Zachary anticipated me :D
digEmAll
A: 

Although the "best" solution has already been found, this is my approach: The XOR ^ Operator (C# Reference) is used to get a hash code for the values sequence, thereby the order does not matter and you do not need a parents-to-parents cross join, no Distinct etc.. You can replace the SelectMany with whatever you want to do with the IGrouping. This is only fail safe for distinct values, because XOR with the same hash code will neutralize them.

var dublicates = parentList 
    .GroupBy(p => p.myValues
        .Select(x => x.ToString().GetHashCode())
        .Aggregate((v1, v2) => v1 ^ v2))
    .Where(g => g.Take(2).Count() == 2)
    .SelectMany(g => g);
Nappy
Hashcode is useless for int, and XOR lead to many collisions. For example: `1^2^3 == 0^0^0`
digEmAll
I added `ToString`, but it only works if the values are distinct.
Nappy
XOR will lead to collisions, so this method it's not reliable. Let's take for example 2 different list: [0,2] and [7,9] they both have aggregated key (i.e. XOR of their string hashcodes) equal to 30, so are identified as equal lists.
digEmAll
A: 

Try this:

var matches = (from p1 in parentList
               from p2 in parentList
               let c1 = p1.myValues
               let c2 = p2.myValues
               where p1 != p2 &&
                     c1.All(child => c2.Contains(child)) &&
                     c2.All(child => c1.Contains(child))
               select p1).Distinct();
Joshua Rodgers
Forgot to mention... this ignores duplicity and order.
Joshua Rodgers
+3  A: 

So you can wrap the logic up and just use GroupBy if you implement an IEqualityComparer:

class IntegerListComparer : IEqualityComparer<List<int>>
{
    #region IEqualityComparer<List<int>> Members

    public bool Equals(List<int> x, List<int> y)
    {
        //bool xContainsY = y.All(i => x.Contains(i));
        //bool yContainsX = x.All(i => y.Contains(i));
        //return xContainsY && yContainsX;
        return x.SequenceEqual(y);
    }

    public int GetHashCode(List<int> obj)
    {
        return 0;
    }

    #endregion
}

Call it like so:

var results = list
    .GroupBy(p => p.MyValues, new IntegerListComparer())
    .Where(g => g.Count() > 1)
    .SelectMany(g => g);
Zachary Yates
+1 I was just writing it in my answer... this is probably the best/right solution. ;)
digEmAll
I'm going to try this, how do I return a List<parent> as opposed to the anon type?
@Zachary: Maybe, you could slightly gain speed by returning obj.Count in GetHashCode, or for example returning first 2-3 elements sum. And, you miss where clause to filter single-occurring parents.
digEmAll
@forcripesake: first you should add `.Where(x => x.Count() > 1)` to filter parents with unique list, then `.SelectMany(x => x).ToList()` to get a list of all parents with list repeated.
digEmAll
{3,2,1} == {1,2,3} == {3,3,3,2,2,1} ?
Nappy
@digEmAll: this is the second comment, claiming an answer to be right/best. But until know your solution is the only fully working one, and far from be the "best".
Nappy
@Nappy I think somewhere the OP said that the lists would be distinct and in the same order.
Zachary Yates
@digEmAll Good point, I'll update.
Zachary Yates
@Nappy: you're right, I didn't notice the wrong Contains usage. Anyway, I still think this is the right approach, it has just to be improved, for example with a simple SequenceEquals in the Equals method.
digEmAll
@Nappy, @digEmAll Thanks for the insight.
Zachary Yates