7

I have list of class of type:

public class MyClass
{        
    public SomeOtherClass classObj;         
    public string BillId;           
}

public List<MyClass> myClassObject;

Sample Values:

BillId = "123",classObj = {},
BillId = "999",classObj = {},
BillId = "777",classObj = {},
BillId = "123",classObj = {}

So in above example, we have duplicate values for BillId. I would like to remove all the duplicate values (Not Distinct) so the result would contain only 999 & 777 value.

One way to achieve this to

  • Loop through all items
  • Get count of unique BillId
  • If count is greater than 1, store that BillId in another variable
  • Loop again and remove item based on BillId

Is there any straightforward way to achieve this?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Shaggy
  • 5,422
  • 28
  • 98
  • 163
  • 3
    You want to remove all pairs that have duplicate values? In your case, you want to remove both 123's? – Joe Phillips Jun 21 '17 at 15:50
  • @JoePhillips That's right – Shaggy Jun 21 '17 at 15:56
  • Related: [Remove List elements that appear more than once, in place](https://stackoverflow.com/questions/36415957/remove-listt-elements-that-appear-more-than-once-in-place) – Theodor Zoulias Jan 17 '23 at 15:21
  • 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). – Theodor Zoulias Jan 18 '23 at 07:53

6 Answers6

16

I think this would work:

var result = myClassObject.GroupBy(x => x.BillId)
    .Where(x => x.Count() == 1)
    .Select(x => x.First());

Fiddle here

maccettura
  • 10,514
  • 3
  • 28
  • 35
4

You can also do this,

var result = myClassObject.GroupBy(x => x.BillId)
              .Where(x => !x.Skip(1).Any())
              .Select(x => x.First());

FIDDLE

Sajeetharan
  • 216,225
  • 63
  • 350
  • 396
1

This may help.

var result = myClassObject
          .GroupBy(x => x.BillId)
          .Where(x => x.Count()==1)
          .Select(x => x.FirstOrDefault());
jAC
  • 5,195
  • 6
  • 40
  • 55
sina_Islam
  • 1,068
  • 12
  • 19
1

The .Where(x => x.Count()==1) wasn't good for me.

You can try:

.GroupBy(x => x.codeLigne).Select(x => x.First()).ToList()
Kaiser
  • 1,957
  • 1
  • 19
  • 28
  • *"The `.Where(x => x.Count()==1)` wasn't good for me."* -- Then you are interested for the [`DistinctBy`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.distinctby) functionality. This question is about removing duplicates altogether, without preserving any of them. Your answer would be valid in [this question](https://stackoverflow.com/questions/489258/linqs-distinct-on-a-particular-property "LINQ's Distinct() on a particular property"). – Theodor Zoulias Jan 18 '23 at 09:19
0

Try this.

var distinctList = myClassObject.GroupBy(m => m.BillId)
                                .Where(x => x.Count() == 1)
                                .SelectMany(x => x.ToList())
                                .ToList();
IvanJazz
  • 763
  • 1
  • 7
  • 19
0

You've asked for a straightforward solution to the problem, and the GroupBy+Where+Select solutions satisfy perfectly this requirement, but you might also be interested for a highly performant and memory-efficient solution. Below is an implementation that uses all the tools that are currently available (.NET 6+) for maximum efficiency:

/// <summary>
/// Returns a sequence of elements that appear exactly once in the source sequence,
/// according to a specified key selector function.
/// </summary>
public static IEnumerable<TSource> UniqueBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> comparer = default)
{
    ArgumentNullException.ThrowIfNull(source);
    ArgumentNullException.ThrowIfNull(keySelector);

    Dictionary<TKey, (TSource Item, bool Unique)> dictionary = new(comparer);
    if (source.TryGetNonEnumeratedCount(out int count))
        dictionary.EnsureCapacity(count); // Assume that most items are unique

    foreach (TSource item in source)
        CollectionsMarshal.GetValueRefOrAddDefault(dictionary, keySelector(item),
            out bool exists) = exists ? default : (item, true);

    foreach ((TSource item, bool unique) in dictionary.Values)
        if (unique)
            yield return item;
}

The TryGetNonEnumeratedCount+EnsureCapacity combination can have a significant impact on the amount of memory allocated during the enumeration of the source, in case the source is a type with well known size, like a List<T>.

The CollectionsMarshal.GetValueRefOrAddDefault ensures that each key will be hashed only once, which can be impactful in case the keys have expensive GetHashCode implementations.

Usage example:

List<MyClass> unique = myClassObject.UniqueBy(x => x.BillId).ToList();

Online demo.

The difference of the above UniqueBy from the built-in DistinctBy LINQ operator, is that the former eliminates completely the duplicates altogether, while the later preserves the first instance of each duplicate element.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104