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

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>

Related

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.

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
});

AsParallel - Before vs After in Linq Where Clause Performance

I'm having a List<Boss> Collection, every Boss has 2 to 10 Assistant Staff. I'm grouping all the Employees including Boss. Now I'm having List<Person>, from this I'm searching "Raj" using Parallel LINQ, where can I place the supportive method AsParallel() to get better performance, Before or After the Where Clause ?
public class Person
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
}
void Main()
{
List<Boss> BossList = new List<Boss>()
{
new Boss()
{
EmpID = 101,
Name = "Harry",
Department = "Development",
Gender = "Male",
Employees = new List<Person>()
{
new Person() {EmpID = 102, Name = "Peter", Department = "Development",Gender = "Male"},
new Person() {EmpID = 103, Name = "Emma Watson", Department = "Development",Gender = "Female"},
}
},
new Boss()
{
EmpID = 104,
Name = "Raj",
Department = "Development",
Gender = "Male",
Employees = new List<Person>()
{
new Person() {EmpID = 105, Name = "Kaliya", Department = "Development",Gender = "Male"},
new Person() {EmpID = 103, Name = "Emma Watson", Department = "Development",Gender = "Female"},
}
}
};
List<Person> result = BossList
.SelectMany(x =>
new[] { new Person { Name = x.Name, Department = x.Department, Gender = x.Gender, EmpID = x.EmpID } }
.Concat(x.Employees))
.GroupBy(x => x.EmpID) //Group by employee ID
.Select(g => g.First()) //And select a single instance for each unique employee
.ToList();
List<Person> SelectedResult = new List<Person>();
// AsParallel() - Before Where Clause
SelectedResult = result.AsParallel().Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant())).ToList();
// AsParallel() - After Where Clause
SelectedResult = result.Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant())).AsParallel().ToList();
}
Core Source Code:
List<Person> SelectedResult = new List<Person>();
// AsParallel() - Before Where Clause
SelectedResult = result.AsParallel().Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant())).ToList();
// AsParallel() - After Where Clause
SelectedResult = result.Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant())).AsParallel().ToList();
Before.
AsParallel helps us to run queries in parallel, which is enabling parallel threads to improve performance. If you put before WHERE clause the filtering will be done in series, and only then will anything be parallelized.
Here is some test code:
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
class AsParallelTest
{
static void Main()
{
var query = Enumerable.Range(0, 1000)
.Select(ProjectionExample)
.Where(x => x > 10)
.AsParallel();
Stopwatch stopWatch = Stopwatch.StartNew();
int count = query.Count();
stopWatch.Stop();
Console.WriteLine("Count: {0} in {1}ms", count,
stopWatch.ElapsedMilliseconds);
query = Enumerable.Range(0, 1000)
.AsParallel()
.Select(ProjectionExample)
.Where(x => x > 10);
stopWatch = Stopwatch.StartNew();
count = query.Count();
stopWatch.Stop();
Console.WriteLine("Count: {0} in {1}ms", count,
stopWatch.ElapsedMilliseconds);
}
static int ProjectionExample(int arg)
{
Thread.Sleep(10);
return arg;
}
}
Result:
Count: 989 in 10574ms
Count: 989 in 1409ms
It's obvious that the first result hasn't been parallelized, where the second has. If you have only one processor core, the results should be close. If you have more than two processor cores, the AsParallel call may increase performance even more.
Also, you can read this article.

Linq: find similar objects from two different lists

I've got two separate lists of custom objects. In these two separate lists, there may be some objects that are identical between the two lists, with the exception of one field ("id"). I'd like to know a smart way to query these two lists to find this overlap. I've attached some code to help clarify. Any suggestions would be appreciated.
namespace ConsoleApplication1
{
class userObj
{
public int id;
public DateTime BirthDate;
public string FirstName;
public string LastName;
}
class Program
{
static void Main(string[] args)
{
List<userObj> list1 = new List<userObj>();
list1.Add(new userObj()
{
BirthDate=DateTime.Parse("1/1/2000"),
FirstName="John",
LastName="Smith",
id=0
});
list1.Add(new userObj()
{
BirthDate = DateTime.Parse("2/2/2000"),
FirstName = "Jane",
LastName = "Doe",
id = 1
});
list1.Add(new userObj()
{
BirthDate = DateTime.Parse("3/3/2000"),
FirstName = "Sam",
LastName = "Smith",
id = 2
});
List<userObj> list2 = new List<userObj>();
list2.Add(new userObj()
{
BirthDate = DateTime.Parse("1/1/2000"),
FirstName = "John",
LastName = "Smith",
id = 3
});
list2.Add(new userObj()
{
BirthDate = DateTime.Parse("2/2/2000"),
FirstName = "Jane",
LastName = "Doe",
id = 4
});
List<int> similarObjectsFromTwoLists = null;
//Would like this equal to the overlap. It could be the IDs on either side that have a "buddy" on the other side: (3,4) or (0,1) in the above case.
}
}
}
I don't know why you want a List<int>, i assume this is what you want:
var intersectingUser = from l1 in list1
join l2 in list2
on new { l1.FirstName, l1.LastName, l1.BirthDate }
equals new { l2.FirstName, l2.LastName, l2.BirthDate }
select new { ID1 = l1.id, ID2 = l2.id };
foreach (var bothIDs in intersectingUser)
{
Console.WriteLine("ID in List1: {0} ID in List2: {1}",
bothIDs.ID1, bothIDs.ID2);
}
Output:
ID in List1: 0 ID in List2: 3
ID in List1: 1 ID in List2: 4
You can implement your own IEqualityComparer<T> for your userObj class and use that to run a comparison between the two lists. This will be the most performant approach.
public class NameAndBirthdayComparer : IEqualityComparer<userObj>
{
public bool Equals(userObj x, userObj y)
{
return x.FirstName == y.FirstName && x.LastName == y.LastName && x.BirthDate == y.BirthDate;
}
public int GetHashCode(userObj obj)
{
unchecked
{
var hash = (int)2166136261;
hash = hash * 16777619 ^ obj.FirstName.GetHashCode();
hash = hash * 16777619 ^ obj.LastName.GetHashCode();
hash = hash * 16777619 ^ obj.BirthDate.GetHashCode();
return hash;
}
}
}
You can use this comparer like this:
list1.Intersect(list2, new NameAndBirthdayComparer()).Select(obj => obj.id).ToList();
You could simply join the lists on those 3 properties:
var result = from l1 in list1
join l2 in list2
on new {l1.BirthDate, l1.FirstName, l1.LastName}
equals new {l2.BirthDate, l2.FirstName, l2.LastName}
select new
{
fname = l1.FirstName,
name = l1.LastName,
bday = l1.BirthDate
};
Instead of doing a simple join on just one property (column), two anonymous objects are created new { prop1, prop2, ..., propN}, on which the join is executed.
In your case we are taking all properties, except the Id, which you want to be ignored and voila:
Output:
And Tim beat me to it by a minute
var similarObjectsFromTwoLists = list1.Where(x =>
list2.Exists(y => y.BirthDate == x.BirthDate && y.FirstName == x.FirstName && y.LastName == x.LastName)
).ToList();
This is shorter, but for large list is more efficient "Intersect" or "Join":
var similarObjectsFromTwoLists =
list1.Join(list2, x => x.GetHashCode(), y => y.GetHashCode(), (x, y) => x).ToList();
(suposing GetHashCode() is defined for userObj)
var query = list1.Join (list2,
obj => new {FirstName=obj.FirstName,LastName=obj.LastName, BirthDate=obj.BirthDate},
innObj => new {FirstName=innObj.FirstName, LastName=innObj.LastName, BirthDate=innObj.BirthDate},
(obj, userObj) => (new {List1Id = obj.id, List2Id = userObj.id}));
foreach (var item in query)
{
Console.WriteLine(item.List1Id + " " + item.List2Id);
}

C# linq group by

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) }

Categories