-1

Let say I have a list of objects with the following properties (FirstName, LastName):

var people = new []
{
    new { FirstName = "John", LastName = "Smith" },
    new { FirstName = "Dale", LastName = "Smith" },
    new { FirstName = "Jermey", LastName = "Ducey" },
}.ToList();

Using Linq how can I return just the object containing "Jeremy, Ducey" if I am search by unique last names.

Distinct() still returns one instance of smith, and every answer leads me to GroupBy() which only return groups properties OR Distinct() which doesn't do what I want.

Theodor Zoulias
  • 24,585
  • 5
  • 40
  • 69
Tronald
  • 1,463
  • 1
  • 13
  • 28
  • Do you literally have that list of strings? Including `"First, Last"` as the first string? Or do you actually have a list of objects with properties `First` and `Last`? – Enigmativity Nov 08 '20 at 02:09
  • Sorry the first is headers I'll edit – Tronald Nov 08 '20 at 02:13
  • So the key you're looking for is last name, having count > 1, distinct. I think that's why answers are leading you to GroupBy as part of the solution. – Mark C. Nov 08 '20 at 02:17
  • I edited the title, to differentiate this question from a [similar one](https://stackoverflow.com/questions/292307/selecting-unique-elements-from-a-list-in-c-sharp "Selecting Unique Elements From a List in C#"). – Theodor Zoulias Nov 08 '20 at 08:44
  • 1
    Hey @Enigmativity! This is a nice question, and it's a pity to be closed as unclear. Could you suggest a way to improve the question so it can be reopened? – Theodor Zoulias Nov 09 '20 at 21:51
  • @TheodorZoulias Classic SO – Tronald Nov 09 '20 at 22:19
  • 1
    Tronald I'm kind of new here, but AFAIK it's getting worse. – Theodor Zoulias Nov 09 '20 at 22:21

2 Answers2

3

This does what you want:

var people = new []
{
    new { FirstName = "John", LastName = "Smith" },
    new { FirstName = "Dale", LastName = "Smith" },
    new { FirstName = "Jermey", LastName = "Ducey" },
}.ToList();

var uniqueLastNames =
    people
        .GroupBy(x => x.LastName)
        .Where(xs => xs.Count() == 1)
        .SelectMany(xs => xs)
        .ToList();

It gives me:

unique

Enigmativity
  • 105,241
  • 11
  • 83
  • 163
  • AFAIK the [`Count()`](https://referencesource.microsoft.com/system.core/system/linq/Enumerable.cs.html#41ef9e39e54d0d0b) operator is implemented with a fast path for collections, and the [`Grouping`](https://referencesource.microsoft.com/system.core/system/linq/Enumerable.cs.html#7bb231e0604c79e3) is an `IList`, so the `xs.Count() == 1` should be quite efficient. – Theodor Zoulias Nov 08 '20 at 07:33
  • 1
    @TheodorZoulias - Yes, that checks out. :-) – Enigmativity Nov 08 '20 at 08:51
1

I was about to suggest using the UniqueBy operator from the MoreLinq library, but paradoxically this operator is missing. It seems that even when you have more Linq, you can never have enough.

The uniqueness is not the same as distinctness. Although the result of both DistinctBy and UniqueBy is a sequence that contains both distinct and unique elements, the difference is about what happened with the items that were not distinct. The DistinctBy keeps the first of the duplicates, while the UniqueBy eliminates them alltogether.

Below is my implementation of the UniqueBy extension method. It should be more efficient than using the GroupBy operator, because it doesn't create IGrouping objects during the enumeration of the source.

/// <summary>
/// Returns all unique elements of the given source, where uniqueness is
/// determined according to a given key selector. Duplicate elements are removed.
/// </summary>
public static IEnumerable<TSource> UniqueBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> keyComparer = default)
{
    // Arguments validation omitted
    keyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
    var dictionary = new Dictionary<TKey, (TSource Item, bool Unique)>(keyComparer);
    foreach (TSource item in source)
    {
        var key = keySelector(item);
        if (!dictionary.TryGetValue(key, out var entry))
        {
            dictionary[key] = (item, true);
        }
        else
        {
            if (entry.Unique) dictionary[key] = (default, false);
        }
    }
    foreach (var (item, unique) in dictionary.Values)
    {
        if (unique) yield return item;
    }
}
Theodor Zoulias
  • 24,585
  • 5
  • 40
  • 69