3
public Material
{
  public MaterialType MaterialKind {get;set;}
}

enum MaterialType
{
   Iron,
   Plastic,
   Carbon,
   Wood
}

I have a list of Material items.

var items = new List<Material>
{ 
  new Material{ MaterialKind = MaterialType.Iron},
  new Material{ MaterialKind = MaterialType.Plastic},
  new Material{ MaterialKind = MaterialType.Carbon},
  new Material{ MaterialKind = MaterialType.Iron},
  new Material{ MaterialKind = MaterialType.Wood},
}

Each type should only be one time available maximum in the material list.

How do I write this:

bool isItemsListDistinct = materials.??

The above sample has at least one duplicate which is "Iron" thus the return value must be false.

Pascal
  • 12,265
  • 25
  • 103
  • 195
  • Check out this SO post, it is a nifty way to get rid of duplicates with linq: http://stackoverflow.com/questions/1606679/remove-duplicates-in-the-list-using-linq – Bryan Mudge Aug 03 '15 at 15:55
  • 1
    Take a look at Enumerable.Distinct here: https://msdn.microsoft.com/en-us/library/vstudio/bb348436(v=vs.100).aspx – ajliptak Aug 03 '15 at 15:56

3 Answers3

5

Something like this (Linq):

bool isItemsListDistinct = !materials
  .GroupBy(item => item)
  .Any(chunk => chunk.Skip(1).Any());

Explanation: chunk.Count() > 1 could bring an overhead (imagine that Count() returns 1234567 and the internal representation is a linked list), so the safier way is to check if there's an item after the 1st one: chunk.Skip(1).Any().

EDIT: Even if the current implementation of GroupBy() in Linq to Objects doesn't have problems with Count():

http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,7bb231e0604c79e3

  internal class Grouping : IGrouping<TKey, TElement>, IList<TElement> {
    ...
    internal int count;      
    ...
    // No overhead in such implementation
    int ICollection<TElement>.Count {
      get { return count; }
    }
    ...
  }

using Any(), Exists (in SQL) etc. instead of Count() when collection is/(can be) dependent on implementation is a good practice.

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • Nice explanation around `chunk.Skip(1).Any()` – Ilya Ivanov Aug 03 '15 at 16:03
  • Nice answer. I am curious, would not the code in my answer execute only the first positive group count, or would it have to count all groups? – Ioannis Karadimas Aug 03 '15 at 16:03
  • @Ioannis Karadimas: *Linq* performs *lazy* execution, so your code stop executing just after a positive group (i.e. with `Count() > 1`) have been found. The problem is that this first positive group could be a *very long one*. – Dmitry Bychenko Aug 04 '15 at 06:25
  • @DmitryBychenko It still has to read in every single item in the input. After that (Edit: *your code's advantage is that*) it doesn't matter if one group is particularly long - it'll only ever read two items from any group. – Rawling Aug 04 '15 at 07:20
  • *Edit to edit* actually, the [results of a `GroupBy`](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,7bb231e0604c79e3) don't need to iterate to do a count. – Rawling Aug 04 '15 at 07:47
  • @Rawling: you're right - in *Linq to Objects* it's the case, `Count()` doesn't bring overhead; however in *Linq to SQL* corresponding `Count` and `Exists` (or `Any`) queries could be very different. Nothing wrong with `Count`, but safier way (and, IMHO, a good habit) is to avoid it when the collection is not evident. – Dmitry Bychenko Aug 04 '15 at 08:12
  • For Linq2Sql this query with `skip` would not generate very good SQL. But for Linq2Objects this is good. – Magnus Aug 04 '15 at 08:14
3

This is a way to go about it:

void Main()
{
    var items = new List<Material>
    { 
    new Material{ MaterialKind = MaterialType.Iron},
    new Material{ MaterialKind = MaterialType.Plastic},
    new Material{ MaterialKind = MaterialType.Carbon},
    new Material{ MaterialKind = MaterialType.Iron},
    new Material{ MaterialKind = MaterialType.Wood},
    };

    Material.IsItemsListDistinct(items).Dump(); 
}

// Define other methods and classes here
public class Material
{
  public MaterialType MaterialKind {get;set;}

  public static bool IsItemsListDistinct(IList<Material> materialsList) {
    return materialsList.GroupBy(x => x.MaterialKind).Any(x => x.Count() > 1);
  }
}

public enum MaterialType
{
   Iron,
   Plastic,
   Carbon,
   Wood
}

(The above is a Linqpad sample, so the .Dump() extension will not compile in any other environment)

The code essentially groups by Material Type, then asks whether any of the grouped entries can be found more than once.

Note that you should name your method something more representative, like IsListDistinctByMaterialType since it more clearly states that it 'll employ the material type for comparing in this way.

Ioannis Karadimas
  • 7,746
  • 3
  • 35
  • 45
3

A quicker approach would be:

var materialsSeen = new HashSet<MaterialType>();
var allDifferent = items.All(i => materialsSeen.Add(i.MaterialKind));

This stops as soon as it sees a duplicate material, rather than reading in the entire input in order to group it.

Rawling
  • 49,248
  • 7
  • 89
  • 127