I have written a code for students class to Rank them according to their marks. The code works accurately and gives the result as
Name:B Marks:30 Rank:1
Name:C Marks:30 Rank:1
Name:A Marks:20 Rank:3
Name:D Marks:10 Rank:4
But I need it to be optimized so it wont take too much of time to be processed. Below is the code
public List<Students> GetRanks(List<Students> students)
{
List<Students> rStudents = new List<Students>();
students = students.OrderByDescending(a => a.marks).ToList();
for (int i = 0; i < students.Count; i++)
{
Students stu = new Students();
stu.Id = students[i].Id;
stu.Name = students[i].Name;
stu.marks = students[i].marks;
if (i > 0 && students[i].marks == students[i - 1].marks)
{
stu.rank = rStudents.Select(a => a.rank).LastOrDefault();
}
else
{
stu.rank = i + 1;
}
rStudents.Add(stu);
}
return rStudents;
}
List<Students> students = new List<Students>() {
new Students() { Id = 1, Name = "A", marks = 20 },
new Students() { Id = 1, Name = "B", marks = 30 },
new Students() { Id = 1, Name = "C", marks = 30 },
new Students() { Id = 1, Name = "D", marks = 10 },
};
List<Students> rStudents = GetRanks(students);
public class Students
{
public int Id { get; set; }
public string Name { get; set; }
public double marks { get; set; }
public int rank { get; set; }
}
foreach (Students s in GetRanks(students))
{
Console.WriteLine($"Name:{s.Name}\tMarks:{s.marks}\tRank:{s.rank}");
}
Remove unnecessary resource.and using yield will have performance impact in large data. Fewer line and more readable.
If you are fetching data from DataBase it's better idea sort them there then fetch them.
public static IEnumerable<Students> GetRanks(List<Students> students)
{
List<Students> rStudents = students.OrderByDescending(a => a.marks).ToList();
for (int i = 0; i < rStudents.Count; i++)
{
if (i > 0 && rStudents[i].marks == rStudents[i - 1].marks)
{
rStudents[i].rank = rStudents[i - 1].rank;
}
else
{
rStudents[i].rank = i + 1;
}
yield return rStudents[i];
}
}
This should be pretty darn fast:
var rank = 1;
students
.GroupBy(x => x.marks)
.OrderByDescending(x => x.Key)
.ToList()
.ForEach(xs =>
{
xs.ToList().ForEach(x => x.rank = rank);
rank += xs.Count();
});
With your sample data I get:
Here's a non-destructive version of the code:
public static List<Students> GetRanks(List<Students> students)
{
var rank = 1;
return
students
.OrderByDescending(x => x.marks)
.GroupBy(x => x.marks)
.SelectMany(xs =>
{
var r = rank;
rank += xs.Count();
return xs.Select(x => new Students()
{
Id = x.Id,
Name = x.Name,
marks = x.marks,
rank = r,
});
})
.ToList();
}
I measured this using 100_000 randomly created students. My code completed in 110 milliseconds. The original code in the question took 64_119 milliseconds.
Yes, this code was nearly 600x faster.
More then 50% improvement if have large collect with a new method called GetRanksV1
namespace StudentPerformance
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
List<Students> students = new List<Students>();
Random _random = new Random();
for (int i = 1; i < 10001; i++)
{
students.Add(new Students() { Id = i, Name = "A" + i, marks = _random.Next(10, 50) });
}
var watch = System.Diagnostics.Stopwatch.StartNew();
var tmp = GetRanks(students);
watch.Stop();
Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");
watch = System.Diagnostics.Stopwatch.StartNew();
var tmp1 = GetRanksV1(students);
watch.Stop();
Console.WriteLine($"Execution Time V1: {watch.ElapsedMilliseconds} ms");
foreach (Students s in tmp1)
{
Console.WriteLine($"Name:{s.Name}\tMarks:{s.marks}\tRank:{s.rank}");
}
Console.ReadLine();
}
public static List<Students> GetRanks(List<Students> students)
{
List<Students> rStudents = new List<Students>();
students = students.OrderByDescending(a => a.marks).ToList();
for (int i = 0; i < students.Count; i++)
{
Students stu = new Students();
stu.Id = students[i].Id;
stu.Name = students[i].Name;
stu.marks = students[i].marks;
if (i > 0 && students[i].marks == students[i - 1].marks)
{
stu.rank = rStudents.Select(a => a.rank).LastOrDefault();
}
else
{
stu.rank = i + 1;
}
rStudents.Add(stu);
}
return rStudents;
}
public static List<Students> GetRanksV1(List<Students> students)
{
int Srno = 0;
var tmp = students.GroupBy(a => a.marks).OrderByDescending(o => o.Key).Select(s => new { Mark = s.Key, Rand = ++Srno, });
var values = from s in students
join t in tmp on s.marks equals t.Mark
select new Students { Id = s.Id, Name = s.Name, marks = s.marks, rank = t.Rand };
return values.ToList();
}
}
public class Students
{
public int Id { get; set; }
public string Name { get; set; }
public double marks { get; set; }
public int rank { get; set; }
}
}
Related
Scores are considered low if they are less than or equal to 5. I want to select students with repeated low scores.
The expected result is:
Andy
Bobby
Cindy
As each of them has repeated low scores.
Question
I got stuck in completing the last expression GroupBy in the Where clause.
Could you make it done?
class Student
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public List<int> Scores { get; set; } = new List<int>();
public static List<Student> GetStudents()
{
return new List<Student>()
{
new Student
{
Id = 1,
Name="Andy",
Scores={1,1,2,2,3,4,5,6,7,8}
},
new Student
{
Id = 2,
Name="Bobby",
Scores={3,3,3,3,4,5}
},
new Student
{
Id = 3,
Name="Cindy",
Scores={1,1,2,2,3,4,5}
},
new Student
{
Id = 4,
Name="Dave",
Scores={1,2,3,4,5,6,7,8,9,10}
}
};
}
}
class Program
{
static void Main()
{
var query = Student.GetStudents()
.Where(s => s.Scores.GroupBy(i => i).????);
foreach (var x in query)
Console.WriteLine(x.Name);
Console.ReadLine();
}
}
I'd do something like this:
var query = Student.GetStudents()
.Where(s => s.Scores
.Where(x => x <= 5)
.GroupBy(i => i)
.Any(x => x.Count() > 1));
Try following :
var query = Student.GetStudents()
.Select(x => new { student = x.Name, scores = x.Scores.GroupBy(y => y).Select(y => new { score = y.Key, count = y.Count() }).ToList() }).ToList();
var lowScore = query.Where(x => x.scores.Any(y => (y.count > 1) && (y.score <= 5))).ToList();
I want to create an array of Tasks, called listTask, with each element of listTask is a Task of type A, Task of type A is created by the function Task.WhenAll. then I do await Task.WhenAll(listTask) But the program does not perform the work in the listTask array. I set debug and those tasks were completed, I don't understand why
namespace Ding.LearningNewThings
{
public class MultiChannelTask
{
public static async Task RunMiltipleChannel()
{
ConcurrentDictionary<int, Channel<Position>> _dataChannels = new ConcurrentDictionary<int, Channel<Position>>();
var listPlace = Place.InitData();
var numberOfPlace = listPlace.Count();
for (int i = 0; i < listPlace.Count(); i++)
{
_dataChannels.TryAdd(i, Channel.CreateUnbounded<Position>());
}
Task[] listStationTask = new Task[numberOfPlace];
for (var j = 0; j < numberOfPlace; j++)
{
var listTask = new Task[2];
var placeOuter = listPlace[j];
listTask[0] = Task.Run(async () =>
{
int IndexOfPlace = j;
var place = new Place()
{
ID = placeOuter.ID,
Name = placeOuter.Name
};
Channel<Position> dataChannel;
var r = new Random();
if (_dataChannels.TryGetValue(IndexOfPlace, out dataChannel))
{
var position = new Position()
{
PlaceID = place.ID,
PlaceName = place.Name,
ID = r.Next(1, 100)
};
await dataChannel.Writer.WriteAsync(position);
Console.WriteLine($"Push postion ID {position.ID}, Place ID {position.PlaceID}");
}
});
listTask[1] = Task.Run(async () =>
{
var IndexOfPlace = j;
Channel<Position> dataChannel;
var r = new Random();
if (_dataChannels.TryGetValue(IndexOfPlace, out dataChannel)) {
var position = await dataChannel.Reader.ReadAsync();
Console.WriteLine($"Get postion ID {position.ID}, Place ID {position.PlaceID}");
}
});
listStationTask[j] = Task.WhenAll(listTask);
}
await Task.WhenAll(listStationTask);
}
}
public class Place
{
public int ID { get; set; }
public string Name { get; set; }
public static List<Place> InitData()
{
var listData = new List<Place>();
for (int i = 0; i < 10; i++)
{
var data = new Place()
{
ID = i,
Name = $"Postion{i}",
};
listData.Add(data);
}
return listData;
}
}
public class Position
{
public int ID { get; set; }
public int PlaceID { get; set; }
public string PlaceName { get; set; }
public string Name { get; set; }
public static List<Position> InitData()
{
var listData = new List<Position>();
for (int i = 0; i < 10; i++)
{
var data = new Position()
{
ID = i,
Name = $"Postion{i}"
};
listData.Add(data);
}
return listData;
}
}
}
I seem the task have had done ahead of intended. Sometimes it works, but I don't know why the job always completes without running in the list task code.
what else do I need to add or change to display my employees between the ages of 25 and 35
years with a higher salary than average salary. This program only displays employees between the ages of 25 and 35.
for (int i = 0; i < employeename; i++)
{
while (br.PeekChar() != -1)
{
function[i].Name= br.ReadString();
function[i].Function= br.ReadString();
function[i].Age= br.ReadInt32();
function[i].salary= br.ReadInt32();
function[i].Studies= br.ReadString();
if ((function[i].Age>25) && (function[i].Age<35))
{
string str = String.Format("|{0,-21}|{1,-9}|{2,-7}|{3,-16}|{4,-12}|", function[i].Name, function[i].Function,
function[i].Age.ToString(), function[i].Salary.ToString(), function[i].Studies);
Console.WriteLine(str);
}
}
}
Console.ReadKey();
br.Close();
I think you need to read all data and calculate avgSalary before filtering:
Map your data in an Employee class with params Name, Function, Age,
Salary, Studies.
var employees = new List<Employee>();
while (br.PeekChar() != -1)
{
var employee = new Employee() {
Name= br.ReadString(),
Function= br.ReadString(),
Age= br.ReadInt32(),
Salary= br.ReadInt32(),
Studies= br.ReadString()
};
employees.Add(employee);
}
var avgSalary = employees.Select(x => x.Salary).Average();
var finalList = employees.Where(x => x.Age > 25 && x.Age < 35 && x.Salary > avgSalary).ToList();
You need to use EF & LINQ for that.
best way this :
first read all and add to a list then do other operation on list
here it is a sample!
sing System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
List<Employee> employees = new List<Employee>();
for (int i = 0; i < 10; i++)
{
employees.Add(new Employee
{
Name ="name "+i,
Function = "fun "+i,
Age =i+24,
Salary =100+i,
Studies ="stu"+i
});
}
//here can do any opr on list
double avg =employees.Average(s => s.Salary);
var result = employees.Where(x => x.Age > 25 && x.Age < 35 && x.Salary> avg).ToList();
foreach (var item in result)
{
Console.WriteLine("item name:" + item.Name);
}
Console.WriteLine("avg :" + avg);
Console.ReadKey();
}
public class Employee
{
public string Name { set; get; }
public string Function { set; get; }
public int Salary { set; get; }
public int Age { set; get; }
public string Studies { set; get; }
}
}
}
I am trying to remove information that is in the third level of a dictionary and can only use it after the removal of this information.
But I can not, what am I doing wrong?
public class Person
{
int id;
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public int ID
{
get { return id; }
set { id = value; }
}
public List<Product> ListProd;
}
public class Product
{
public int idProd;
public string Description;
public List<Tax> listTax;
}
public class Tax
{
public int idTax;
public string Value;
}
//Method
public void SomeMethod()
{
Dictionary<int, List<int>> dicRemove = new Dictionary<int, List<int>>();
List<int> listTaxToRemove = new List<int>();
for (int m = 8; m < 10; m++)
{
listTaxToRemove.Add(m);
}
dicRemove.Add(10, listTaxToRemove);
Dictionary<int, List<Person>> dic = new Dictionary<int, List<Person>>();
List<Person> list = new List<Person>();
for (int i = 0; i < 2; i++)
{
Person d = new Person();
d.ID = i;
d.Name = "Person " + i;
d.ListProd = new List<Product>();
for (int j = 3; j < 6; j++)
{
Product p = new Product();
p.idProd = j;
p.Description = "Product " + j;
d.ListProd.Add(p);
p.listTax = new List<Tax>();
for (int m = 7; m < 10; m++)
{
Tax t = new Tax();
t.idTax = m;
t.Value = "Tax " + m;
p.listTax.Add(t);
}
}
list.Add(d);
}
dic.Add(10, list);
var q = dic.Select(s => s.Value
.Select(s1 => s1.ListProd
.Select(s2 => s2.listTax
.RemoveAll(r =>!dicRemove[s.Key].Contains(r.idTax))))).ToList();
}
I tried a number of ways, through iterations, this approach had just deleting unnecessary records.
Thank you!
RemoveAll is not invoked due to deferred execution of Select
you have 3 Select and only one ToList()
q has type System.Collections.Generic.List'1[System.Collections.Generic.IEnumerable'1[System.Collections.Generic.IEnumerable'1[System.Int32]]]
there are nested IEnumerable object which were not enumerated
here is a working variant but absolutely unreadable, don't do it like this
var q = dic.Select(s => s.Value.Select(s1 => s1.ListProd.Select(s2 => s2.listTax.RemoveAll(r=>!dicRemove[s.Key].Contains(r.idTax)))
.ToList())
.ToList())
.ToList();
improved variant
foreach(var pair in dic)
foreach(var person in pair.Value)
foreach(var prod in person.ListProd)
prod.listTax.RemoveAll(t=>!dicRemove[pair.Key].Contains(t.idTax));
How can I group by with multiple columns using lambda?
I saw examples of how to do it using linq to entities, but I am looking for lambda form.
var query = source.GroupBy(x => new { x.Column1, x.Column2 });
I came up with a mix of defining a class like David's answer, but not requiring a Where class to go with it. It looks something like:
var resultsGroupings = resultsRecords.GroupBy(r => new { r.IdObj1, r.IdObj2, r.IdObj3})
.Select(r => new ResultGrouping {
IdObj1= r.Key.IdObj1,
IdObj2= r.Key.IdObj2,
IdObj3= r.Key.IdObj3,
Results = r.ToArray(),
Count = r.Count()
});
private class ResultGrouping
{
public short IdObj1{ get; set; }
public short IdObj2{ get; set; }
public int IdObj3{ get; set; }
public ResultCsvImport[] Results { get; set; }
public int Count { get; set; }
}
Where resultRecords is my initial list I'm grouping, and its a List<ResultCsvImport>. Note that the idea here to is that, I'm grouping by 3 columns, IdObj1 and IdObj2 and IdObj3
if your table is like this
rowId col1 col2 col3 col4
1 a e 12 2
2 b f 42 5
3 a e 32 2
4 b f 44 5
var grouped = myTable.AsEnumerable().GroupBy(r=> new {pp1 = r.Field<int>("col1"), pp2 = r.Field<int>("col2")});
Further to aduchis answer above - if you then need to filter based on those group by keys, you can define a class to wrap the many keys.
return customers.GroupBy(a => new CustomerGroupingKey(a.Country, a.Gender))
.Where(a => a.Key.Country == "Ireland" && a.Key.Gender == "M")
.SelectMany(a => a)
.ToList();
Where CustomerGroupingKey takes the group keys:
private class CustomerGroupingKey
{
public CustomerGroupingKey(string country, string gender)
{
Country = country;
Gender = gender;
}
public string Country { get; }
public string Gender { get; }
}
class Element
{
public string Company;
public string TypeOfInvestment;
public decimal Worth;
}
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>()
{
new Element { Company = "JPMORGAN CHASE",TypeOfInvestment = "Stocks", Worth = 96983 },
new Element { Company = "AMER TOWER CORP",TypeOfInvestment = "Securities", Worth = 17141 },
new Element { Company = "ORACLE CORP",TypeOfInvestment = "Assets", Worth = 59372 },
new Element { Company = "PEPSICO INC",TypeOfInvestment = "Assets", Worth = 26516 },
new Element { Company = "PROCTER & GAMBL",TypeOfInvestment = "Stocks", Worth = 387050 },
new Element { Company = "QUASLCOMM INC",TypeOfInvestment = "Bonds", Worth = 196811 },
new Element { Company = "UTD TECHS CORP",TypeOfInvestment = "Bonds", Worth = 257429 },
new Element { Company = "WELLS FARGO-NEW",TypeOfInvestment = "Bank Account", Worth = 106600 },
new Element { Company = "FEDEX CORP",TypeOfInvestment = "Stocks", Worth = 103955 },
new Element { Company = "CVS CAREMARK CP",TypeOfInvestment = "Securities", Worth = 171048 },
};
//Group by on multiple column in LINQ (Query Method)
var query = from e in elements
group e by new{e.TypeOfInvestment,e.Company} into eg
select new {eg.Key.TypeOfInvestment, eg.Key.Company, Points = eg.Sum(rl => rl.Worth)};
foreach (var item in query)
{
Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Points.ToString());
}
//Group by on multiple column in LINQ (Lambda Method)
var CompanyDetails =elements.GroupBy(s => new { s.Company, s.TypeOfInvestment})
.Select(g =>
new
{
company = g.Key.Company,
TypeOfInvestment = g.Key.TypeOfInvestment,
Balance = g.Sum(x => Math.Round(Convert.ToDecimal(x.Worth), 2)),
}
);
foreach (var item in CompanyDetails)
{
Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Balance.ToString());
}
Console.ReadLine();
}
}