Group of objects with similar names - c#

I have an object:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
I return a list that may look like the following:
var students = new List<Student>() {
new Student(){ Id = 1, Name="Bill85"},
new Student(){ Id = 2, Name="Bill66"},
new Student(){ Id = 3, Name="Ram7895"},
new Student(){ Id = 4, Name="Ram5566"},
new Student(){ Id = 5, Name="Join1230"}
};
I want to group them together if they have similar names, to get the result like below, is there any quick way to solve this?
GroupStudentList
UserList
Id = 1, Name="Bill85"
Id = 2, Name="Bill66"
UserList
Id = 3, Name="Ram7895"
Id = 4, Name="Ram5566"
UserList
Id = 3, Name="Join1230"

public class Student
{
public List<Student> ListGroupStudent {get; set;}
//add one list property...
}
var listGroup = listStudent.GroupBy(
x => x.Name,
(key, y) => new { ListGroupStudent = y.ToList() });

Making the assumption that similar names refers to identical names when all digits are removed from each name (as suggested by #klaus-gütter), a simple implementation could be the following:
Group the students based on their Name, but stripped for digits (using Regex)
For each group of similarily named students, select the students in that group to constitute a sublist
resulting in a nested List of Students:
List<List<Student>> groupStudentList = students
.GroupBy(
s => Regex.Replace(s.Name, "[0-9]*", string.Empty),
(similarName, students) => students.ToList())
.ToList();
The Regex expression above basically says "Replace all (if any) digits between 0 and 9 that exist in s.Name with an empty string". This means that if you have two students named "Tom1" and "T3o4m", they will also be grouped together -- because both names stripped for digits will be "Tom".
Optionally, you could create a UserList class:
public class UserList
{
public List<Student> Students { get; set; }
}
and create a UserList object for each grouping of students based on their name similarity:
List<UserList> groupStudentList = students
.GroupBy(
s => Regex.Replace(s.Name, "[0-9]*", string.Empty),
(similarName, students) => new UserList { Students = students.ToList() })
.ToList();

Related

Get list of matching objects from 3 different lists

I'm exercising on one task. Basically i got 3 different lists in C# code side. Class employee is containing the skills list. And also have list of employees as well.
for example :
class Employee
{
List<Skills> Skills;
}
class Skills
{
int Id;
int Name;
}
class Where_Im_Working_Currently
{
List<Employee> employees ;
}
What I'm trying to achieve is to get list of common skills from every individual. suppose we have 3 employees and all 3 employees have JAVA skill in common such that { id = x , name = JAVA }.
so all 3 employees have skills similar with id and name needs to be fetched out.
NOTE : I'm trying to get all matching skills and not just subset of skills
for e.g.
Case 1: Perfect match. (Get List having a, b, c)
list 1 => a, b, c
list 2 => a, b, c
list 3 => a, b, c
Case 1: No match. (Get Null list)
list 1 => a, b, c
list 2 => a, b,
list 3 => b, c
following is the query i have come up with :
var skills= employees.Select(x => x.Skills.Select(p => p.Id== x.Skill[0].Id && p.Name == x.Skill[0].Name));
but this will give IEnumerable that's where its getting wrong and im unable to form LINQ.
Any pointers or help are welcomed.
This might not be the most optimized way of doing this, but here's a solution that outputs the skills that are common for all employees.
The key is to use SelectMany to get to the child lists.
public class Employee
{
public List<Skills> Skills { get; set; }
}
public class Skills
{
public int Id { get; set; }
public string Name { get; set; }
}
[Test]
public void GetSomeSkills()
{
var employees = new List<Employee>
{
new Employee { Skills = new List<Skills> { new Skills { Id = 1, Name = "Java" }, new Skills { Id = 2, Name = "C#" } } },
new Employee { Skills = new List<Skills> { new Skills { Id = 1, Name = "Java" }, new Skills { Id = 3, Name = "Cooking" } } },
new Employee { Skills = new List<Skills> { new Skills { Id = 1, Name = "Java" } } },
//new Employee { Skills = new List<Skills> { new Skills { Id = 4, Name = "C++" } } }
};
var allSkills = employees.SelectMany(x => x.Skills).ToList();
Console.WriteLine(string.Join(", ", allSkills.Select(x => x.Name)));
// Output: Java, C#, Java, Cooking, Java
var commonSkills = employees.SelectMany(e =>
e.Skills.Where(s => employees.All(e2 => e2.Skills.Select(x => x.Id).Contains(s.Id)))).ToList();
Console.WriteLine(string.Join(", ", commonSkills.Select(x => x.Name)));
// Output: Java, Java, Java
}
If you uncomment the last Employee you would have a zero result, as there would no longer be a skill that is common for all employees.
Also you probably want to get distinct result, but it sounds like you already know how to do that.
Edit after original question was modified:
The below outputs only the skills that everyone has, if you uncomment the last Employee you would have null as result.
[Test]
public void GetSomeSkills()
{
var employees = new List<Employee>
{
new Employee { Skills = new List<Skills> { new Skills { Id = 1, Name = "Java" }, new Skills { Id = 2, Name = "C#" } } },
new Employee { Skills = new List<Skills> { new Skills { Id = 1, Name = "Java" }, new Skills { Id = 2, Name = "C#" } } },
// new Employee { Skills = new List<Skills> { new Skills { Id = 1, Name = "Java" } } },
};
bool HasSameSkills(Employee first, Employee second)
{
var firstIds = first.Skills.Select(x => x.Id).OrderBy(x => x).ToList();
var secondIds = second.Skills.Select(x => x.Id).OrderBy(x => x).ToList();
return firstIds.SequenceEqual(secondIds);
}
var commonSkills = employees.FirstOrDefault(x => employees.All(y => HasSameSkills(x, y)))?.Skills;
Console.WriteLine(string.Join(", ", (commonSkills ?? new List<Skills>()).Select(x => x.Name)));
// Output: Java, C#
}

C#: find objects within array with same id and sum their quantity values. Add those to the new object

I have an array or list of objects returned from the Database. Let's take this as an example:
this is the class:
public class products
{
public string name { get; set; }
public int quantity { get; set; }
public long id{ get; set; }
}
List<Product> prod = new List<Product>()
prod = (call to the DB to get data back...)
array is returned with object of products
What I need is to loop through this array and add up the quantities for the same ids. Meaning, add up 2+7 for id 3 and add that data to another object so that new object would have something like: total: 9, id: 3
then same again for id 5, total: 7, id: 5
and so on.
I am at a loss of the right way to do this. I was looking at Linq but the only way I used it is by providing specific values. Please help me with this
`
foreach (var p in prod){ Now what do i do?}
`
The easiest way is with GroupBy and Sum (Both use System.Linq):
List<Product> products = new List<Product>()
{
new Product(){Id = 1, Cost = 20.0M },
new Product(){Id = 1, Cost = 30.0M },
new Product(){Id = 2, Cost = 20.0M },
new Product(){Id = 3, Cost = 20.0M },
new Product(){Id = 3, Cost = 5.0M }
};
products.GroupBy(g => g.Id).ToList().ForEach(e =>
{
Console.WriteLine($"Total: {
e.Sum(s => s.Cost)} for {e.Key}");
});
Edit
With the new information provided: You can do this to a concrete class:
public class Product
{
public string name { get; set; }
public int quantity { get; set; }
public long id{ get; set; }
}
List<Product> products = new List<Product>();
var products = (call to the DB to get data back...).GroupBy(g => g.Id).ToList().ForEach(e =>
{
products.Add(new Product()
{
Id = e.Key,
Quantity = e.Sum(s => s.Quantity)
})
});
Per your code snippet, prod is single product of type products.
So, assuming your code which invokes database call returns prod list something like below:
List<products> prod = new List<products>();
prod = _productRepository.GetProductData(prodId);
You can use linq GroupBy (please include System.Linq to use GroupBy linq extension method) to find the total quantity of each product like below:
var anonymousList = prod.GroupBy(p => p.id).Select(g => new {id = g.Key, totalQuantity = g.Sum(p => p.quantity)}).ToList()
The above returns anonymous list of objects where each object contains id and totalQuantity.
If you are interested in dictionary of product id vs totalQuantity, then use something like below:
Dictionary<long, int> dictionary = prod.GroupBy(p => p.id).ToDictionary(k => k.Key, v => v.Sum(p => p.quantity));
UPDATE based on comments discussion:
You can invoke GroupBy on prod without verifying the count. No exception will be thrown.

Remove elements in a list considering duplicated subelements

I need to remove elements in a single list considering one or more duplicated subelement
Classes
public class Person
{
public int id { get; set; }
public string name { get; set; }
public List<IdentificationDocument> documents { get; set; }
public Person()
{
documents = new List<IdentificationDocument>();
}
}
public class IdentificationDocument
{
public string number { get; set; }
}
Code:
var person1 = new Person() {id = 1, name = "Bob" };
var person2 = new Person() {id = 2, name = "Ted" };
var person3 = new Person() {id = 3, name = "Will_1" };
var person4 = new Person() {id = 4, name = "Will_2" };
person1.documents.Add(new IdentificationDocument() { number = "123" });
person2.documents.Add(new IdentificationDocument() { number = "456" });
person3.documents.Add(new IdentificationDocument() { number = "789" });
person4.documents.Add(new IdentificationDocument() { number = "789" }); //duplicate
var personList1 = new List<Person>();
personList1.Add(person1);
personList1.Add(person2);
personList1.Add(person3);
personList1.Add(person4);
//more data for performance test
for (int i = 0; i < 20000; i++)
{
var personx = new Person() { id = i, name = Guid.NewGuid().ToString() };
personx.documents.Add(new IdentificationDocument() { number = Guid.NewGuid().ToString() });
personx.documents.Add(new IdentificationDocument() { number = Guid.NewGuid().ToString() });
personList1.Add(personx);
}
var result = //Here comes the linq query
result.ForEach(r => Console.WriteLine(r.id + " " +r.name));
Expected result:
1 Bob
2 Ted
3 Will_1
Example
https://dotnetfiddle.net/LbPLcP
Thank you!
You can use the Enumerable.Distinct<TSource> method from LINQ. You'll need to create a custom comparer to compare using the subelement.
See How do I use a custom comparer with the Linq Distinct method?
Well, yes, you could use a custom comparer. But that's going to be lots more code than your specific example requires. If your specific example is all you need, this this will work fine:
var personDocumentPairs = personList1
.SelectMany(e => e.documents.Select(t => new {person = e, document = t}))
.GroupBy(e => e.document.number).Select(e => e.First());
var result = personDocumentPairs.Select(e => e.person).Distinct();
along the lines of Adam's solution the trick is to iterate persons and group them by associated document numbers.
// persons with already assigned documents
// Will_2
var duplicate = from person in personList1
from document in person.documents
group person by document.number into groupings
let counter = groupings.Count()
where counter > 1
from person in groupings
.OrderBy(p => p.id)
.Skip(1)
select person;
// persons without already assigned documents
// Bob
// Ted
// Will_1
var distinct = from person in personList1
from document in person.documents
group person by document.number into groupings
from person in groupings
.OrderBy(p => p.id)
.Take(1)
select person;
the orderby is a made up rule for the already assigned documents persons, but your mileage may vary

How to get proper data using LINQ Lambda expression with many to many relation

Looks like a very simple case, but I can't find a proper lambda expression for it in my head or in stackoverflow :( I appreciate all help.
Case looks simple. I have two classes and relation many to many between them.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public List<Document> Documents { get; set; }
}
public class Document
{
public int Id { get; set; }
public string Name { get; set; }
public List<Student> Students { get; set; }
}
Then I initiate some values:
Student st1 = new Student { Id = 1, Name = "Student 1" };
Student st2 = new Student { Id = 2, Name = "Student 2" };
Student st3 = new Student { Id = 3, Name = "Student 3" };
List<Student> listStudent12 = new List<Student>();
listStudent12.Add(st1);
listStudent12.Add(st2);
List<Student> listStudent23 = new List<Student>();
listStudent23.Add(st2);
listStudent23.Add(st3);
Document doc1 = new Document { Id = 1, Name = "doc 1", Students = listStudent12 };
Document doc2 = new Document { Id = 2, Name = "doc 2", Students = listStudent23 };
List<Document> listDocs = new List<Document>();
listDocs.Add(doc1);
listDocs.Add(doc2);
Now I would like to get a list of documents using linq lambda expression, which are related to "Student 3" (id: 3).
I tried like this:
var result = listDocs.Where(d => d.Students.FirstOrDefault().Id == 3).ToList();
but it returns null (I guess I know why - the first returned student is not equal to 3).
I expect the result to contain all documents which has in list of students student with Id = 3.
I stucked here and need a help. Thank you in advance for any.
On the SQL level I would go:
SELECT Document.* from Document, DocumentStudent WHERE Document.Id = DocumentStudent.DocumentId AND DocumentStudent.StudentId = 3
What you did in your example was finding any Document that has first Student with Id: 3. That ends up with none.
What you want to do is:
var result = listDocs.Where(doc => doc.Students.Any(st => st.Id == 3).ToList();
It will evaluate to any Document that has at least one Student with Id: 3.
I think it will look simpler if you use the following syntax
var result = (from doc in listDocs
from student in doc.Students
where student.Id == 3
select doc).ToList();

Filtering a List using lambda

I have an object which has properties ID, brandID, brandName, NumPages, and Type.
i need to show the top 5 brands by numPage size, a brand may have multiple IDs, so I need to group by brand
listing.OrderByDescending(o => o.numPage).GroupBy(o=> o.brandName).Take(5).ToList();
is alone the lines of what im looking for but this is not valid code.
It sounds like a given brand name may have several ID's and that you want the top 5 brand's sorted by numPage. Is that correct
If so try the following
var query = listing
.GroupBy(x => x.brandName)
.OrderByDescending(brands => brands.Sum(x => x.numPage))
.Select(x => x.Key)
.Take(5);
Note: After the GroupBy operation you're now passing around a collection of the brand objects instead of single ones. Hence to order by the numPage we need to sum it for all of the brand objects in the group. The .Select(x => x.Key) will select back out the original brandName on which the group is based
just tried and it works:
public class Listing
{
public int ID { get; set; }
public int BrandID { get; set; }
public string BrandName { get; set; }
public int NumPages { get; set; }
public Type Type { get; set; }
}
Here the filtering
Listing listing1 = new Listing() { NumPages = 2, BrandName = "xx" };
Listing listing2 = new Listing() { NumPages = 2, BrandName = "xx" };
Listing listing3 = new Listing() { NumPages = 2, BrandName = "xx" };
Listing listing4 = new Listing() { NumPages = 3, BrandName = "xxxxx" };
List<Listing> allListings = new List<Listing>() { listing1, listing2, listing3, listing4 };
var result = allListings.OrderByDescending(x => x.NumPages).GroupBy(x => x.BrandName).Take(5);

Categories

Resources