user2818430
user2818430

Reputation: 6029

Get a list of duplicates from list based on properties comparison

I have a List<Demo>

public class Demo
{
    public Demo()
    {

    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Title { get; set; }
}

The Id property is unique for every record in the list.

How can I get a List<Demo> from the original List<Demo> with all the duplicates where the Name and Title are same.

What I did so far but what I get is one single record:

List<Demo> demo = new List<Demo>();

demo.Add(new Demo()
{
    Id = 1,
    Name = "Demo",
    Title = "A"
});

demo.Add(new Demo()
{
    Id = 2,
    Name = "Demo",
    Title = "A"
});

demo.Add(new Demo()
{
    Id = 3,
    Name = "Demo",
    Title = "A"
});

demo.Add(new Demo()
{
    Id = 4,
    Name = "Demo1",
    Title = "A"
});

demo.Add(new Demo()
{
    Id = 5,
    Name = "Demo2",
    Title = "A"
});

and then i am doing:

var duplicates = demo.GroupBy(t => new { t.Name, t.Title })
                     .Where(t => t.Count() > 1)
                     .Select(g => g.Key).ToList();

From the example above I should get a List<Demo> with the first 3 items where the ids are:1,2,3 because Name and Title are same.

Upvotes: 5

Views: 3795

Answers (4)

Harald Coppoolse
Harald Coppoolse

Reputation: 30454

You were almost there.

Groupby divides a sequence into groups. Each group has something in common: the Key of the group. Each group is a sequence containing all elements in the original sequence that match the key.

Your key is new { t.Name, t.Title }. Your original sequence will be divided into groups of object that had the same name / title:

  • Group Demo / A contains three elements: Id = 1, Id = 2, Id = 3
  • Group Demo1 / A contains only element Id = 4
  • Group Demo2 / A contains only element Id = 5

The Where after you group returns a sequence of IGrouping. This sequence contains only one element: the only group that has more than one element. This is the group with key Demo/A.

Your specification was not a sequence of IGrouping (where each group is a sequence of Demo), but one list of Demo that contains all elements with duplicate Name/Title.

This means you have to take the elements from all groups after your Where (in your example this sequence contains only one group) and concatenate all these groups into one sequence. This is done by Enumerable.SelectMany

IEnumerable<Demo> duplicates = demo
    .GroupBy(demoElement => new {demoElement.Name, demoElement.Title})
    .Where(group => group.Skip(1).Any())
    .SelectMany(group => group);

The SelectMany takes the sequence of each group and concatenates all sequences into one sequence.

By the way, did you notice that I didn't use Count() to detect if there are duplicates, but Skip(1).Any(). If one of your groups would have hundreds of elements, it would be enough to stop counting after the first element. It is a waste of computing power to count all elements to detect if there is more than one.

One final hint: don't use ToList() if you are not certain you need it. If the user of your piece of code only wants the first element, or the first few, it is a waste if you calculate all elements into a list. Keep it IEnumerable<Demo> as long as possible.

Upvotes: 3

Pramod Sutar
Pramod Sutar

Reputation: 70

Below code can help you ,

List duplicates = demo.GroupBy(grp => new { grp.Name, grp.Title }).SelectMany(selm => selm.Skip(1)).Distinct().ToList();

It is working fine as per your expected answer.

Upvotes: 0

Nikhil Agrawal
Nikhil Agrawal

Reputation: 48558

Hers's what you want if you have more than 1 title and names are same. Say the last object is Demo1 and A.

var duplicates = demo.GroupBy(t => new { t.Name, t.Title }).Where(t => t.Count() > 1)
                     .Select(x => new { Count = x.Count(), Values = x.Select(y => y)})
                     .ToList();

What this does it, gives you count and all the grouped values (based on Name and Title) and their Count.

enter image description here

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1499800

It sounds like all you're missing is a SelectMany call. Currently you're creating all the appropriate groups and filtering down to groups with more than one entry - but if you want a single flat list, you need to flatten those groups back to their elements:

var duplicates = demo
    .GroupBy(t => new { t.Name, t.Title })
    .Where(t => t.Count() > 1)
    .SelectMany(x => x) // Flatten groups to a single sequence
    .ToList();

Note that this doesn't mean every entry in the resulting list will have the same name and title. It does mean that every entry will have a name/title combination in common with at least one other entry.

Upvotes: 17

Related Questions