2

I have a class say

public class Sample 
{
    public string property;
    public List<string> someListProperty;
    public string someOtherPropery;
}

Now I have a object which is List<Sample>, I need to iterate over this collection and find out items which are having same values for property and someListProperty fields.

I tried this:

var listSample = List<Sample>()
var result = listSample.GroupBy(x => new { x.property, x.someListProperty})
                       .Where(x => x.Count() > 1).ToList();

But this doesn't seem to work, any pointer would be greatly appreciated.

Rand Random
  • 7,300
  • 10
  • 40
  • 88
Vinodh
  • 385
  • 1
  • 4
  • 15

2 Answers2

2

Update after comment below: As what you describe is that you want to group by someOtherProperty instead of someListProperty then just group by it:

listSample.GroupBy(x => new { x.property, x.someOtherProperty});

  1. Option 1 - You should use the SequenceEqual to check that the nested lists of two given samples are the same. To do that pass a custom IEqualityComparer

    public class SampleComparer : IEqualityComparer<Sample>
    {
        public bool Equals(Sample x, Sample y)
        {
            return x.property == y.property &&
                Enumerable.SequenceEqual(x.someListProperty, y.someListProperty);
        }
        public int GetHashCode(Sample obj)
        {
            // Implement
        }
    }
    

    (for implementing the GetHashCode see: What is the best algorithm for an overridden System.Object.GetHashCode?)

    and then:

    var result = list.GroupBy(k => k, new SampleComparer());
    

    tested on the following data and it returns 3 groups:

    List<Sample> a = new List<Sample>()
    {
        new Sample { property = "a", someListProperty = new List<string> {"a"}, someOtherPropery = "1"},
        new Sample { property = "a", someListProperty = new List<string> {"a"}, someOtherPropery = "2"},
        new Sample { property = "a", someListProperty = new List<string> {"b"}, someOtherPropery = "3"},
        new Sample { property = "b", someListProperty = new List<string> {"a"}, someOtherPropery = "4"},
    };
    
  2. Option 2 - Instead of creating a class implementing the interface is to use the ProjectionEqualityComparer as explained here: Can you create a simple 'EqualityComparer<T>' using a lambda expression


As a side note instead of using Count in the where use:

var result = list.GroupBy(k => k, new SampleComparer())
                 .Where(g => g.Skip(1).Any());

As all you want is just to check there is more than one item in the group but not the actual amount this will just go through two items instead of counting them all in an O(n) operation.

Gilad Green
  • 36,708
  • 7
  • 61
  • 95
  • 2
    thanks for the `g.Skip(1).Any()` - thats neat for enumerations, on arrays or lists Length or Count would still be valid though. – Patrick Artner Nov 15 '17 at 19:07
  • @PatrickArtner - that is true as they store a value indicating the size. Thus retrieving it is an `O(1)` operation. In enumerators it needs to be calculated as one goes - so it is an `O(n)` operation. – Gilad Green Nov 15 '17 at 19:21
  • @Gilad Green: I wanted it to group only based on property and someOtherProperty, in that case i will need only two groups returned. from your exmaple it should be :only new Sample { property = "a", someListProperty = new List {"a"}, someOtherPropery = "1"}, new Sample { property = "a", someListProperty = new List {"a"}, someOtherPropery = "2"}, – Vinodh Nov 16 '17 at 00:48
  • @Vinodh - See update. Your question describes you wanted to group by the list and not the other property.. – Gilad Green Nov 16 '17 at 06:19
0

Adapt your linq like this:

    static void Main(string[] args)
    {
        var samples = new List<Sample>
        {
            new Sample("p1", "aaa,bbb,ccc,ddd"),
            new Sample("p1", "bbb,ccc,xxx"),
            new Sample("p2", "aaa,bbb,ccc"),
            new Sample("p1", "xxx")
        };

        var grp = samples.GroupBy(b => b.property)
            .Where(a => a.Key == "p1")
            .SelectMany(s => s.ToList())
            .Where(b => b.someListProperty.Contains("ccc"));

        foreach (var g in grp)
            System.Console.WriteLine(g.ToString());

        System.Console.ReadLine();
    }

    private class Sample
    {
        public string property;

        public List<string> someListProperty;

        public string someOtherPropery;

        public Sample(string p, string props)
        {
            property = p;
            someListProperty = props.Split(',').ToList();
            someOtherPropery = string.Concat(from s in someListProperty select s[0]);
        }

        public override string ToString()
        {
            return $"{property} - {string.Join(", ", someListProperty)} -"
                       + $" ({someOtherPropery})";
        }
    }

Misread - I read you wanted that "some" of the someListProperty should be in for the ones you want to filter from the grouping.

You could always group like this to make it happen:

var grp = samples
    // choose a joiner thats not in your somePropertyList-data
    .GroupBy(b => $"{b.property}:{string.Join("|", b.someListProperty)}")
    .Where(g => g.Skip(1).Any())
    .SelectMany(s => s.ToList())

But be aware that this is kindof hackish and depends on your data. You essentially group over all that is interesting for you and choose all that have multiple results.

Patrick Artner
  • 50,409
  • 9
  • 43
  • 69