Find unique value in group of groups - c#

Guess i have two groups
using System.Collections.Generic;
namespace ConsoleApplication
{
internal class Student
{
public string Name;
public string Surname;
public int Mark;
}
internal class Program
{
private static void Main()
{
var twoGroups = new List<List<Student>>
{
new List<Student>
{
new Student { Name = "Anna", Surname = "Mors", Mark = 4 },
new Student { Name = "Jimmy", Surname = "Lemon", Mark = 4 }
},
new List<Student>
{
new Student { Name = "Anna", Surname = "Mors", Mark = 4 },
new Student { Name = "Tommy", Surname = "Wojski", Mark = 4 },
new Student { Name = "Jimmy", Surname = "Lemon", Mark = 4 }
}
};
}
}
}
How can i get unique persons from both groups (by Name or any other property) with linq?
It's obvious there is Tommy, but i dont know how can i pick him with linq (or pick a list of unique persons.
UPD
Also, can i pick group contain only unique student.
using System.Collections.Generic;
namespace ConsoleApplication
{
internal class GroupOfStudents
{
public string Code;
public List<Student> Students;
}
internal class Student
{
public string Name;
public string Surname;
public int Mark;
}
internal class Program
{
private static void Main()
{
var twoGroups = new List<GroupOfStudents>
{
new GroupOfStudents
{
Code = "A1",
Students = new List<Student>
{
new Student { Name = "Anna", Surname = "Mors", Mark = 4 },
new Student { Name = "Jimmy", Surname = "Lemon", Mark = 4 }
}
},
new GroupOfStudents()
{
Code = "B2",
Students = new List<Student>
{
new Student { Name = "Anna", Surname = "Mors", Mark = 4 },
new Student { Name = "Tommy", Surname = "Wojski", Mark = 4 },
new Student { Name = "Jimmy", Surname = "Lemon", Mark = 4 }
}
}
};
}
}
}
I think i need to pick student and push him to new Group with same name, right?

flaten the nested lists into one SelectMany()
group by unique criterias GroupBy() (I choose all properties)
select unique records Where()
code:
List<Student> result = twoGroups.SelectMany(x => x)
.GroupBy(x => new { x.Name, x.Surname, x.Mark })
.Where(x => x.Count() == 1)
.Select(x => x.First())
.ToList();

You can use SelectMany, GroupBy and Count
IEnumerable<Student> uniqueStudents = twoGroups
.SelectMany(list => list)
.GroupBy(student => student.Name)
.Where(g => g.Count() == 1)
.Select(g => g.First());
This selects only students which aren't contained multiple times in one of both(or both) lists.
If you want to group-by multiple properties use an anonymous type
.GroupBy(student => new { student.Name, student.Surname })
If you want to allow duplicates in the same list but not across multiple lists:
IEnumerable<Student> uniqueStudents = twoGroups
.SelectMany((list, index) => list.Select(student => new { student, index }))
.GroupBy(x => x.student.Name)
.Where(g =>
{
int index = g.First().index;
return g.Skip(1).All(x => x.index == index);
})
.Select(g => g.First().student);
Acc. your Edit
If you want to remove all students from the lists which appear also in other groups:
var studentNameLookup = twoGroups.SelectMany(g => g.Students).ToLookup(s => s.Name);
twoGroups.ForEach(g => g.Students.RemoveAll(s => studentNameLookup[s.Name].Count() > 1));

Related

Convert Linq Query Expression into Linq Lambda Expression

I have written this Linq Query for two class stduents and universites to extract students those are in XXX university.
I like to use the linq lambda expression instead query. I tried to convert but failed. Can anyone help.
IEnumerable<Student> Students = from student in this.students
join university in universities
on student.UniversityId equals university.Id
where university.Name == "XXX"
select student;
Now, How should I use Where operator?
var students = this.students.Join(universities,
s => s.UniversityId,
u => u.Id,
(std, uni) => std);
You can use below code sample.
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
class University
{
public int Id { get; set; }
public string Name { get; set; }
}
class Student
{
public int Id { get; set; }
public int UniversityId { get; set; }
}
class Program
{
static void Main(string[] args)
{
IList<University> universities = new List<University>
{
new University { Id = 1, Name = "u1" },
new University { Id = 2, Name = "u2" }
};
IList<Student> students = new List<Student>
{
new Student { Id = 1, UniversityId = 1 },
new Student { Id = 2, UniversityId = 1 },
new Student { Id = 3, UniversityId = 2 },
new Student { Id = 4, UniversityId = 2 }
};
IEnumerable<Student> stus = universities
.Join(students, university => university.Id, student => student.UniversityId, (university, student) => new { university, student })
.Where(j => j.university.Name == "u2")
.Select(j => j.student);
}
}
}

group by and merge some field result in linq

I have a class like
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Now I have a list of this class: List<Person> persons;
var persons = new List<Person> {
new Person { Id = 1, LastName = "Reza", FirstName="Jenabi" },
new Person { Id = 1, LastName = "Amin", FirstName="Golmahalle"},
new Person { Id = 2, LastName = "Hamed", FirstName="Naeemaei"}
};
Is there a way I can group by Id and get the list of all the full Name (Combine first and last names)?
So after grouping:
var Id = results[0].Id; // Output : 1
List<string> fullNames = results[0].FullNames; // Output : "Reza Jenabi","Amin Golmahalle"
I believe this is what you need:
var results = persons.GroupBy(x => x.Id)
.Select(x => new { Id = x.Key, FullNames = x.Select(p => $"{p.FirstName} {p.LastName}").ToList() })
.ToList();
I think bellow code can help you:
var fff = from p in persons
group $"{p.FirstName} {p.LastName}" by p.Id into g
select new { PersonId = g.Key, FullNames = g.ToList() };
yeah, you can use GroupBy and Join those items:
var grouped = persons.GroupBy(p => p.Id)
.Select(s => string.Join(", ", s.Select(a=> $"{a.FirstName} {a.LastName}")));

Linq Query for most occuring property of a class

I know this is really basic but how do I implement a linq query that returns the most occuring field?
This is what I got so far:
var commonAge = from c in customers.GroupBy(s=>s.age)
.OrderByDescending(sg=>sg.Count())
.Take(1)
select s.Key;
Based on your comment, you are looking for the most common Age on a customer data type which has said property.
// Start with the customers collection
var mostCommonAge = customers
// then group by their age,
.GroupBy(c => c.Age,
// and project into a new anonymous type
(key, g) => new {Age = key, Count = g.Count()})
// order by count of each age
.OrderByDescending(g => g.Count)
// and take the first
.First();
Here's a complete working example. With a data model class Customer:
class Customer {
public string Name { get; set; }
public int Age { get;set; }
}
Then you can output the most common age by
static void Main(string[] args) {
var customers = new List<Customer> {
new Customer { Age = 23 },
new Customer { Age = 23 },
new Customer { Age = 23 },
new Customer { Age = 24 },
new Customer { Age = 25 }
};
var mostCommonAge = customers
.GroupBy(c => c.Age,
(key, g) => new {Age = key, Count = g.Count()})
.OrderByDescending(g => g.Count)
.First();
Console.WriteLine(mostCommonAge);
}

Fill object from dataset using grouping in Linq

I am trying to load the data from datatable to objects using Linq. Below is my scenario. I have below table structure and data:
seq name id class
1 Rajesh 101 B
1 kumar 102 B
1 sandeep 104 A
2 Myur 105 B
2 Bhuvan 106 C
3 Siraz 107 A
Below is my class structures
public class student
{
public string name {get;set;}
public string id { get; set; }
public string meritClass { get; set; }
}
public class stdGroup
{
public int seqId{get;set;}
public List<student> students;
}
As a final output I should get a Student constructed for each seq. stdGroup object should be created grouping by seq [three objects].
Example:
stdGroup object 1 would contain 3 student objects
stdGroup object 2 would contain 2 student objects
Can anyone please help me.
This should do what you need (assuming by DataTable you mean DataTable):
List<stdGroup> stdGroups = myDataTable
.AsEnumerable()
.GroupBy(a => a.Field<int>("Seq"), a => new student() { id = a.Field<string>("Id"), name = a.Field<string>("name"), meritClass = a.Field<string>("class") })
.Select(a => new stdGroup() { seqId = a.Key, students = a.ToList() })
.ToList();
To break it down, firstly, get the datatable rows into a state where we can use linq,
.AsEnumerable()
Now, do the groupings - selecting seq as the key for the group, and build a student object for every entry, which will get assigned to the corresponding group.
.GroupBy(a => a.Field<int>("Seq"), a => new student() { id = a.Field<string>("Id"), name = a.Field<string>("name"), meritClass = a.Field<string>("class") })
Now, for each group create the stdGroup object, and populate the seq property from our group keys, and take the content of each group, and assign that to the students property.
.Select(a => new stdGroup() { seqId = a.Key, students = a.ToList() })
Finally, and optionally, convert to a list instead of enumerable.
.ToList();
You can also check out my implementation:
public class dbStudent
{
public int seq;
public string name;
public int id;
public string meritClass;
}
public class student
{
public string name { get; set; }
public int id { get; set; }
public string meritClass { get; set; }
}
public class stdGroup
{
public int seqId { get; set; }
public List<student> students { get; set; }
}
class Program
{
static void Main(string[] args)
{
var dbStudebts = new List<dbStudent>();
dbStudebts.Add(new dbStudent { seq = 1, name = "Rajesh", id = 101, meritClass = "B" });
dbStudebts.Add(new dbStudent { seq = 1, name = "kumar", id = 102, meritClass = "B" });
dbStudebts.Add(new dbStudent { seq = 1, name = "sandeep", id = 104, meritClass = "A" });
dbStudebts.Add(new dbStudent { seq = 2, name = "Myur", id = 105, meritClass = "B" });
dbStudebts.Add(new dbStudent { seq = 2, name = "Bhuvan", id = 106, meritClass = "C" });
dbStudebts.Add(new dbStudent { seq = 3, name = "Siraz", id = 107, meritClass = "A" });
var result = (from o in dbStudebts
group o by new { o.seq } into grouped
select new stdGroup()
{
seqId = grouped.Key.seq,
students = grouped.Select(c => new student()
{
name = c.name,
id = c.id,
meritClass = c.meritClass
}).ToList()
}).ToList();
}
}

How to make select query by string property name in lambda expression?

I would like to make a query by using lambda select,
Like below:
public class Foo{
public int Id {get;set;}
public string Name {get;set;}
public string Surname {get;set;}
}
var list = new List<Foo>();
var temp = list.Select(x=> x("Name"),("Surname"));
The property name needs to be sent as a string,
I dont know how to use, I have given it for being a example.
is it possible?
Edit:
Foo list :
1 A B
2 C D
3 E F
4 G H
I don't know type of generic list, I have property name such as "Name", "Surname"
I want to be like below:
Result :
A B
C D
E F
G H
The following code snippet shows 2 cases. One filtering on the list, and another creating a new list of anonymous objects, having just Name and Surname.
List<Foo> list = new List<Foo>();
var newList = list.Select(x=> new {
AnyName1 = x.Name,
AnyName2 = x.Surname
});
var filteredList = list.Select(x => x.Name == "FilteredName" && x.Surname == "FilteredSurname");
var filteredListByLinq = from cust in list
where cust.Name == "Name" && cust.Surname == "Surname"
select cust;
var filteredByUsingReflection = list.Select(c => c.GetType().GetProperty("Name").GetValue(c, null));
Interface
If you have access to the types in question, and if you always want to access the same properties, the best option is to make the types implement the same interface:
public interface INamable
{
string Name { get; }
string Surname { get; }
}
public class Foo : INamable
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
This will preserve type safety and enable queries like this one:
public void ExtractUsingInterface<T>(IEnumerable<T> list) where T : INamable
{
var names = list.Select(o => new { Name = o.Name, Surname = o.Surname });
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
If, for some reason, you can't alter the original type, here are two more options.
Reflection
The first one is reflection. This is Mez's answer, i'll just rephrase it with an anonymous type like in the previous solution (not sure what you need exactly):
public void ExtractUsingReflection<T>(IEnumerable<T> list)
{
var names = list.Select(o => new
{
Name = GetStringValue(o, "Name"),
Surname = GetStringValue(o, "Surname")
});
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
private static string GetStringValue<T>(T obj, string propName)
{
return obj.GetType().GetProperty(propName).GetValue(obj, null) as string;
}
Dynamic
The second uses dynamic:
public void ExtractUsingDynamic(IEnumerable list)
{
var dynamicList = list.Cast<dynamic>();
var names = dynamicList.Select(d => new
{
Name = d.Name,
Surname = d.Surname
});
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
With that in place, the following code:
IEnumerable<INamable> list = new List<Foo>
{
new Foo() {Id = 1, Name = "FooName1", Surname = "FooSurname1"},
new Foo() {Id = 2, Name = "FooName2", Surname = "FooSurname2"}
};
ExtractUsingInterface(list);
// IEnumerable<object> list... will be fine for both solutions below
ExtractUsingReflection(list);
ExtractUsingDynamic(list);
will produce the expected output:
FooName1 FooSurname1
FooName2 FooSurname2
FooName1 FooSurname1
FooName2 FooSurname2
FooName1 FooSurname1
FooName2 FooSurname2
I'm sure you can fiddle with that and get to what you are trying to achieve.
var temp = list.Select(x => x.Name == "Name" && x.Surname == "Surname");
var temp = list.Select(x => new {Name = x.Name, Surname = x.Surname});

Categories