Reputation: 6029
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
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:
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
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
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.
Upvotes: 0
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