0

I have a list of objects that have a name field on them.

I want to know if there's a way to tell if all the name fields are unique in the list.

I could just do two loops and iterate over the list for each value, but I wanted to know if there's a cleaner way to do this using LINQ?

I've found a few examples where they compare each item of the list to a hard coded value but in my case I want to compare the name field on each object between each other and obtain a boolean value.

Heinzi
  • 167,459
  • 57
  • 363
  • 519
J.L
  • 51
  • 2
  • 7

2 Answers2

7

A common "trick" to check for uniqueness is to compare the length of a list with duplicates removed with the length of the original list:

bool allNamesAreUnique = myList.Select(x => x.Name).Distinct().Count() == myList.Count();
  • Select(x => x.Name) transforms your list into a list of just the names, and
  • Distict() removes the duplicates.

The performance should be close to O(n), which is better than the O(n²) nested-loop solution.


Another option is to group your list by the name and check the size of those groups. This has the additional advantage of telling you which values are not unique:

var duplicates = myList.GroupBy(x => x.Name).Where(g => g.Count() > 1);

bool hasDuplicates = duplicates.Any();  // or
List<string> duplicateNames = duplicates.Select(g => g.Key).ToList();
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • 1
    Code only answers are not very helpful – maccettura Jun 18 '18 at 15:47
  • 1
    @maccettura: I fully agree. I usually answer quickly with a code-only answer (to prevent others from wasting time and effort into writing the same answer) and then expand it with explanations. You were just too quick in spotting my answer. :-) – Heinzi Jun 18 '18 at 15:48
  • 1
    A good strategy - I was just starting to write up the same answer when you posted. – Sam W Jun 18 '18 at 15:51
  • 1
    @J.L There's a similar solution using `GroupBy` and checking for any groups bigger than 1. This has the added benefit of telling what the duplicate *is* – BradleyDotNET Jun 18 '18 at 16:02
  • @BradleyDotNET: Good point, I have added this option to my answer. – Heinzi Jun 18 '18 at 16:08
1

While you can use LINQ to group or create a distinct list, and then compare item-wise with the original list, that incurs a bit of overhead you might not want, especially for a very large list. A more efficient solution would store the keys in a HashSet, which has better lookup capability, and check for duplicates in a single loop. This solution still uses a little bit of LINQ so it satisfies your requirements.

static public class ExtensionMethods
{
    static public bool HasDuplicates<TItem,TKey>(this IEnumerable<TItem> source, Func<TItem,TKey> func)
    {
        var found = new HashSet<TKey>();
        foreach (var key in source.Select(func))
        {
            if (found.Contains(key)) return true;
            found.Add(key);
        }
        return false;
    }
}

If you are looking for duplicates in a field named Name, use it like this:

var hasDuplicates = list.HasDuplicates( item => item.Name );

If you want case-insensitivity:

var hasDuplicates = list.HasDuplicates( item => item.Name.ToUpper() );
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • I like your solution because it stops traversing the list as soon as the first duplicate is found. (I don't like `This` starting with an uppercase letter, but that might just be me. [Linq uses `source`](https://msdn.microsoft.com/en-us/library/bb291976(v=vs.110).aspx) as a name for the "this" parameter.) – Heinzi Jun 19 '18 at 07:54