1

I have a list of objects

public class Obj
{
    public int Num {get;set;}
    public List<string> StrList {get;set;}
    ...other irrelevant properties
}

Some items in the list of Obj will contain the same StrList but will have a different Num. I would like to remove all of the Obj in the list that have similar StrList and keep the one with the largest Num.

Here is my attempt:

objList = objList.GroupBy(x => new { x.StrList }).OrderByDescending(x => x.Select(y => y.Num)).Select(x => x.First()).ToList();

But I am getting an error: At least one object must implement IComparable.

What is the best way to do this?

Nikolay Kostov
  • 16,433
  • 23
  • 85
  • 123
Mike
  • 2,299
  • 13
  • 49
  • 71
  • 1
    You've got a lot of extra stuff there not needed. `new { x.StrList }` should just be x.StrList. No need to do a select inside your orderbydesending either. – crthompson Jan 06 '15 at 18:17
  • 1
    Mike do a google search on the following error that you have `least one object must implement IComparable` here as some results you can thumb through https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=c%23%20least%20one%20object%20must%20implement%20icomparable – MethodMan Jan 06 '15 at 18:18
  • 2
    `list.GroupBy(x => x.StrList).Select(g => g.OrderByDescending(x => x.Num).First())` – L.B Jan 06 '15 at 18:22

2 Answers2

2
  1. There's no reason to wrap the list in an anonymous type when grouping; just project out the list itself when calling GroupBy.

  2. List hasn't overridden Equals and GetHashCode, so it will uses the list's reference, not its contents, to define equality. You'll need to create a custom IEqualityComparer to compare the values of the sequence.

  3. You're ordering the collection of groups, and those groups aren't comparable, hence your error. You want to be ordering the items within each group. To do this use a Select and then order the group that you are projecting (and take the first of the resulting query) rather than ordering the outer collection.

Community
  • 1
  • 1
Servy
  • 202,030
  • 26
  • 332
  • 449
1

You cannot group by a list, even an ordered one, but you can group by a single string produced by concatenating the strings in a specific order. Assuming that there is a character that never appears in any of the strings (say, '|'), you could do it like this:

objList = objList
    .GroupBy(x => string.Join("|", x.StrList.OrderBy(s => s)))
    .Select(g => g.OrderByDescending(x => x.Num).First())
    .ToList();

The idea is to construct a grouping key from individual strings by concatenating them in the same order (ascending lexicographical).

This should work fine for small amounts of data. If your lists are extremely large, you could implement a special class to use as a multi-part key for grouping without making potentially large keys:

public class MultipartKey : IEquitable<MultipartKey> {
    private IList<string> parts;
    int hashCode;
    public MultipartKey(IEnumerable<string> parts) {
        this.parts = parts.OrderBy(s => s).ToList();
        hashCode = this.parts.Aggregate(0, (p, v) => 31*p + v.GetHashCode());
    }
    public override int GetHashCode() {
        return hashCode;
    }
    public override bool Equals(MultipartKey other) {
        return parts.SequenceEqual(other.Parts);
    }
}

and using it in a query like this:

objList = objList
    .GroupBy(x => new MultipartKey(x.StrList))
    .Select(g => g.OrderByDescending(x => x.Num).First())
    .ToList();
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523