C# linq group by - c#

How do I count, group and sort the following list based on a persons money with linq?
Person[] names = { new Person{ Name = "Harris", Money = 100 },
new Person{ Name = "David", Money = 100 },
new Person{Name = "Harris", Money = 150},
new Person{Name = "Mike", Money = 100},
new Person{Name = "Mike", Money = 30},
new Person{Name = "Mike", Money = 20} };
The result would return:
Harris 250
Mike 150
David 100

var personMoney = names.GroupBy(x=>x.Name)
.Select(x=>new {Name = x.Key, AllMoney = x.Sum(y=>y.Money)})
.OrderByDescending(x=>x.AllMoney).ToList();

from p in names
group p by p.Name into g
order by g.Key
select new { Name = g.Key, Amount = g.Sum(o => o.Amount) }

Related

Best average student(s) score (C#, LINQ, without loops)

Can I somehow calculate the average for different items and choose student(s) with best GPA?
public static List<Student> LoadSampleData()
{
List<Student> output = new List<Student>();
output.Add(new Student { ID = 1, FirstName = "Tim", LastName = "Corey ", Patronymic = "Fitzpatrick ", Group = "A", Math = 5, Programming = 5, Informatics = 5});
output.Add(new Student { ID = 2, FirstName = "Joe", LastName = "Smith ", Patronymic = "Mackenzie ", Group = "A", Math = 3, Programming = 3, Informatics = 4});
output.Add(new Student { ID = 3, FirstName = "Ellie", LastName = "Williams ", Patronymic = "", Group = "B", Math = 4, Programming = 5, Informatics = 4});
output.Add(new Student { ID = 4, FirstName = "Joel", LastName = "Miller ", Patronymic = "", Group = "B", Math = 4, Programming = 4, Informatics = 5});
return output;
}
I need it to be calculated approximately according to the following logic (finding the average for all subjects for each student. For example: student_avarage(Math+Programming+Informatics) and find the best score). Without using loops like: for, while, if and etc. ("foreach{}" too)
public static void BestStudentsAvarage()
{
List<Student> students = ListManager.LoadSampleData();
var StudentAverage =
from student in students
group student by student.ID into studentID
select new
{
ID = studentID.Key,
student_Average = studentID.Average(x => x.(Math+Programming+Informatics))
};
var bestGrade = StudentAverage.Max(gr => gr.student_Average);
var bestIDs_1 = StudentAverage.Where(g => g.student_Average == bestGrade);
var bestID_1 = bestIDs_1.FirstOrDefault();
Console.WriteLine($"\nBest student(s) GPA: {bestID_1.ID} \nScore: {bestID_1.student_Average}");
Console.ReadLine();
}
I think this is what you actually want(divide the sum of the three subjects through 3):
public static List<(Student student, decimal average)> BestStudentsAvarage(List<Student> students)
{
return students
.Select(s => (Student:s,Average:(s.Math+s.Programming+s.Informatics)/3m))
.GroupBy(g => g.Average)
.OrderByDescending(g => g.Key)
.First()
.ToList();
}
List<Student> sample = LoadSampleData();
List<(Student student, decimal average)> bestAvarageStudents = BestStudentsAvarage(sample);
foreach(var x in bestAvarageStudents)
{
Console.WriteLine($"Best student <{x.student.FirstName} {x.student.LastName}> with Average <{x.average}>");
}
With your example it would output: Best student <Tim Corey> with Average <5>

How to get a value from dicitionary in c#

static void Main(string[] args)
{
List<People> people = new List<People>(){
new People(){FirstName = "aaa", LastName = "zzz", Age = 3, Location = "Berlin"},
new People(){FirstName = "aaa", LastName = "yyy", Age = 6, Location = "Paris"},
new People(){FirstName = "bbb", LastName = "zzz", Age = 5, Location = "Texas"},
new People(){FirstName = "bbb", LastName = "yyy", Age = 4, Location = "Sydney"},
new People(){FirstName = "ccc", LastName = "zzz", Age = 2, Location = "Berlin"},
new People(){FirstName = "ccc", LastName = "yyy", Age = 3, Location = "New York"},
new People(){FirstName = "aaa", LastName = "xxx", Age = 2, Location = "Dallas"},
new People(){FirstName = "bbb", LastName = "www", Age = 6, Location = "DC"},
new People(){FirstName = "ccc", LastName = "vvv", Age = 3, Location = "Detroit"},
new People(){FirstName = "ddd", LastName = "uuu", Age = 5, Location = "Gotham"}
};
var dict = people
.GroupBy(x => (x.FirstName, x.LastName))
.ToDictionary(x => x.Key,
x => x.ToList());
/**
how to get a value from dictionary when i just have first name.
i want to get all value from dict where name = "aaa"
**/
}
public class People
{
public string FirstName {get; set;}
public string LastName {get; set;}
public int Age {get; set;}
public string Location {get; set;}
}
is there a way to get a value from dictionary with just 1 key (example i just have name "aaa", and i want to get all people with Firstname "aaa"). i can get it with where but there is no point in using dictionary. should i used nested dictionary or there's other way ?
There is no point using the dictionary, I'm not sure what you're trying to achive by using it? Why not just use the list as mcjmzn said?
List<people> peopleCohort = people.Where(p=> p.FirstName == "aaa").ToList();
Think you might be overthinking it.
Stu.
UPDATE:
Given the following test:
SqlCommand com = new SqlCommand("SELECT Name FROM [Responses_PersonalData]", con);
con.Open();
List<Person> listPeople = new List<Person>();
Dictionary<string, Person> dicPeople = new Dictionary<string, Person>();
using (con)
{
Random rand = new Random();
using (SqlDataReader reader = com.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
//Only use data where we have firstname, surname, approx 49,000 names in db.
string[] name = reader["Name"].ToString().Trim().Split(' ');
if (name.Length == 2)
{
Person person = new Person() { Age = rand.Next(0, 100), FirstName = name[0], LastName = name[1], Location = name[1] };
listPeople.Add(person);
}
}
}
}
}
//Creates approx 100 million people exponentially.
for (int i = 1; i < 12; i++)
listPeople.AddRange(listPeople);
//Group by firstname lastname tuple
var tuppleDicPeople = listPeople
.GroupBy(x => (x.FirstName, x.LastName))
.ToDictionary(x => x.Key,
x => x.ToList());
//Method 1
List<Person> listPeopleCohortResults = listPeople.FindAll(p => p.FirstName == "Dean");
//Method 2
List<Person> dicPeopleCohortResults = tuppleDicPeople.Where(kvp => kvp.Key.FirstName == "Dean").SelectMany(kvp => kvp.Value).ToList();
Findings:
The group by operation is very expensive.
listPeople.FindAll(p => p.FirstName == "Dean"); => 1651ms, returns 32768 results.
List dicPeopleCohortResults = tuppleDicPeople.Where(kvp => kvp.Key.FirstName == "Dean").SelectMany(kvp => kvp.Value).ToList(); => 10ms, returns 32768 results.
If you can afford the expense of the group by then your solution is optimal given the limited research I've done.
Stu.

Updating One List with values from another list based on a condition

I have two lists lets say:
List1 = [{"name":"john", "limit":"21"},{"name":"edith", "limit":"21"}, {"name":"sam", "limit":"50"}]
List2 = [{"name":"john", "limit":"21"},{"name":"john", "limit":"21"} {"name":"edith", "limit":"21"}, {"name":"sam", "limit":"30"}, {"name":"sam", "limit":"30"}]
What is the best way to update elements in List2 with values from elements in List1 based on the condition that the value of name in List1 is equal to value of name in List2
I'm hoping I can find a solution using LINQ, like one below used to select elements
var list = List2.FindAll(y => List1.Any(x => y.name==x.name));
Since your condition is a simple "equals" condition, you can use a join
from source in list1
join target in list2 on source.name equals target.name
select new {target.name, source.limit}
Here's a simple test based on the data from the question:
var list1 = new List<dynamic>{new {name = "john", limit = "21"}, new {name = "edith", limit = "21"}, new {name = "sam", limit = "50"}};
var list2 = new List<dynamic>{new {name = "john", limit = "21"}, new {name = "john", limit = "21"}, new {name = "edith", limit = "21"}, new {name = "sam", limit = "30"}, new {name = "sam", limit = "30"}};
var query = from source in list1
join target in list2 on source.name equals target.name
select new {target.name, source.limit};
foreach (var x in query)
{
Console.WriteLine($"name = {x.name}, limit = {x.limit}");
}
Outputs:
name = john, limit = 21
name = john, limit = 21
name = edith, limit = 21
name = sam, limit = 50
name = sam, limit = 50

Lambda expression groupby on single column and fetch all column orderby count

Here my query is I have a list with repeated data and on that, I want to do groupby clause on FName column and display in order by descending of a count and display all record of that particular list
List<Employee> empList = new List<Employee>();
empList.Add(new Employee() { ID = 1, FName = "John", Age = 23, Sex = 'M' });
empList.Add(new Employee() { ID = 2, FName = "Mary", Age = 25, Sex = 'F' });
empList.Add(new Employee() { ID = 3, FName = "John", Age = 28, Sex = 'M' });
empList.Add(new Employee() { ID = 4, FName = "Amber", Age = 23, Sex = 'M' });
empList.Add(new Employee() { ID = 5, FName = "Kathy", Age = 25, Sex = 'M' });
empList.Add(new Employee() { ID = 6, FName = "Lena", Age = 27, Sex = 'F' });
empList.Add(new Employee() { ID = 7, FName = "John", Age = 28, Sex = 'M' });
empList.Add(new Employee() { ID = 8, FName = "Kathy", Age = 27, Sex = 'F' });
var dup1 = empList
.GroupBy(x => new { x.FName })
.Select(group => new { Name = group.Key.FName, Count = group.Count() })
.OrderByDescending(x => x.Count);
foreach (var x in dup1)
{
Console.WriteLine(x.Count + " " + x.Name);
}
From the above code I am getting output like this:
But what i actually want is like this:
It looks like you want the group count, and then the information about the first item in the group. If that's the case, then you can simply use GroupBy to group the items, and then in your output just capture and display the information for the first item in the group:
var groups = empList.GroupBy(e => e.FName).OrderByDescending(group => group.Count());
foreach (var group in groups)
{
var first = group.First();
Console.WriteLine($"{group.Count()} {first.FName}\t{first.Age} {first.Sex}");
}
Output
.Select(group => new
{
Name = group.Key.FName,
Count = group.Count(),
Age = group.First().Age,
Sex = group.First().Gender
});

Sorting an IEnumerable in LINQ

How to sort the given examples.
IEnumerable<extra> eList = new List<extra>()
{
new extra{ id = 1, text = "a"},
new extra{ id = 2, text = "g"},
new extra{ id = 3, text = "i"},
new extra{ id = 4, text = "e"},
new extra{ id = 5, text = "f"},
new extra{ id = 6, text = "d"},
new extra{ id = 7, text = "c"},
new extra{ id = 8, text = "h"},
new extra{ id = 9, text = "b"}
};
IEnumerable<sample> sam = new List<sample>()
{
new sample{ id = 1, name = "sample 1", list = new List<int>{1,5,6}},
new sample{ id = 2, name = "sample 1", list = new List<int>{2,9}},
new sample{ id = 3, name = "sample 1", list = new List<int>{8,3,7}},
new sample{ id = 4, name = "sample 1", list = new List<int>{3,4,8}},
new sample{ id = 5, name = "sample 1", list = new List<int>{1,5,7}},
new sample{ id = 6, name = "sample 1", list = new List<int>{6,9,7}}
};
I have this code to sort and join the sample list to the extra object above.
var s2 = (from d1 in sam
select new
{
name = d1.name,
id = d1.id,
list =
(
from d2 in d1.list
join e in eList on d2 equals e.id
select new {
id = d2, text = e.text
}
).OrderBy(item => item.text.FirstOrDefault())
});
The code above works fine, it joined the two data and sorted the values for the list. But what I want is the output above 's2' will be sorted again by its 'list' value by 'list.text'.
So possible output above must be:
{ id = 1, name = "sample 1", list = {'a','f','d'}},
{ id = 5, name = "sample 1", list = {'a','f','c'}},
{ id = 2, name = "sample 1", list = {'g','b'}},
{ id = 4, name = "sample 1", list = {'i','e','h'}},
{ id = 6, name = "sample 1", list = {'d','b','c'}},
{ id = 3, name = "sample 1", list = {'h','i','c'}},
Is this possible in LINQ?
thanks
var newsam = sam.Select(s => new
{
id = s.id,
name = s.name,
list = s.list
.Select(l => eList.FirstOrDefault(e => e.id == l).text)
.OrderBy(e => e)
.ToList()
}
).OrderBy(s => s.list.FirstOrDefault())
.ToList();
EDIT
So, the inner lists are sorted by the text value of the eList; and the outer list is sorted by the first element of the inner list
EDIT
var s2=(from d1 in sam
select new
{
name = d1.name,
id = d1.id,
list =
(
from d2 in d1.list
join e in eList on d2 equals e.id
select e.text
).OrderBy(item => item).ToList()
}).OrderBy(item => item.list.FirstOrDefault());

Categories