May someone convert following linq query syntax into method syntax using lambda? - c#

IList<Student> studentList = new List<Student>() {
new Student() { StudentID = 1, StudentName = "John", Age = 18, StandardID = 1 } ,
new Student() { StudentID = 2, StudentName = "Steve", Age = 21, StandardID = 1 } ,
new Student() { StudentID = 3, StudentName = "Bill", Age = 18, StandardID = 2 } ,
new Student() { StudentID = 4, StudentName = "Ram" , Age = 20, StandardID = 2 } ,
new Student() { StudentID = 5, StudentName = "Ron" , Age = 21 }
};
IList<Standard> standardList = new List<Standard>() {
new Standard(){ StandardID = 1, StandardName="Standard 1"},
new Standard(){ StandardID = 2, StandardName="Standard 2"},
new Standard(){ StandardID = 3, StandardName="Standard 3"}
};
var studentsWithStandard = from stad in standardList
join s in studentList
on stad.StandardID equals s.StandardID
into sg
from std_grp in sg
orderby stad.StandardName, std_grp.StudentName
select new {
StudentName = std_grp.StudentName,
StandardName = stad.StandardName
};
foreach (var group in studentsWithStandard)
{
Console.WriteLine("{0} is in {1}", group.StudentName, group.StandardName);
}
i tried. my code is following.
var studentsWithStandard = standardList.GroupJoin(studentList, stand => stand.StandardID, s => s.StandardID,
(stand, students) => new {StandardName = stand.StandardName, studentGroup = students}).OrderBy(an => an.StandardName);
output will be like this:
John is in Standard 1
Steve is in Standard 1
Bill is in Standard 2
Ram is in Standard 2
i got it from http://www.tutorialsteacher.com/codeeditor?cid=cs-JUmITE
Thanks in advance.

In my experience join is the one place where query syntax is more readable than lambda syntax, but regardless...
I would highly recommend reading Jon Skeet's excellent book C# In Depth. The chapters on LINQ give very clear explanations of what various query syntaxes translate to. https://www.manning.com/books/c-sharp-in-depth-third-edition
A join expression that only has one from will translate to the Join method, not GroupJoin. GroupJoin is used when you have two froms before the join.
You would want this to do an inner join:
standardList
.Join(studentList,
stad => stad.StandardID,
s => s.StandardID,
(stad, s) => new { Standard = stad, Student = s })
.OrderBy(x => x.Standard.StandardName)
.ThenBy(x => x.Student.StudentName)

You can translate the query like this:
var ans = standardList.Join(studentList, stad => stad.StandardID, s => s.StandardID, (stad, s) => new { stad, s })
.OrderBy(stads => stads.stad.StandardName).ThenBy(stads => stads.s.StudentName)
.Select(stads => new { stads.s.StudentName, stads.stad.StandardName });
Note that #JamesFaix's answer provides a more efficient less literal version that combines the Join and Select.
Which is actually the query comprehension version without into which isn't needed for your query:
var studentsWithStandard = from stad in standardList
join s in studentList on stad.StandardID equals s.StandardID
orderby stad.StandardName, s.StudentName
select new {
StudentName = s.StudentName,
StandardName = stad.StandardName
};
Note a strict translation of your query would involve GroupJoin/SelectMany but it isn't necessary since you aren't try to do a left join:
var ans2 = standardList.GroupJoin(studentList, stad => stad.StandardID, s => s.StandardID, (stad, sg) => new { stad, sg })
.SelectMany(stadsg => stadsg.sg.Select(s => new { stadsg.stad, s }))
.OrderBy(stads => stads.stad.StandardName).ThenBy(stads => stads.s.StudentName)
.Select(stads => new { stads.s.StudentName, stads.stad.StandardName });

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 call generic method like Find, FindAll

I an intermediate C# develop. I am trying to implement some methods in my program. But it's been giving sleepless nights. E.g
NB: I already declared the class properties ahead.
Employee employe = new Employee(){
ID = 111,
Name = "Eric Trump",
Gender = "Male",
Salary = 900000
};
Employee employe2 = new Employee()
{
ID = 112,
Name = "Ayo",
Gender = "Female",
Salary = 8900
};
List<Employee> listemp = new List<Employee>();
listemp.Add(employe);
listemp.Add(employe2);
How to i use the Find, FindAll() or FindLast()?
You can do that by passing a Predicate<T> delegate to Find, FindLast or FindAll methods
List<Employee> listemp = new List<Employee>();
listemp.Add(employe);
listemp.Add(employe2);
var result = listemp.FindLast(e => e.ID == 112); //or listemp.Find(e => e.ID == 112)
e => e.ID == 112 is called lambda expression, it's just a more convenient way to specify an anonymous delegate, you can find more details at Delegates and lambdas
Just use lambda-expressions:
List<string> lists = new List<string>()
{
"1", "2", "3"
};
var all = lists.FindAll(s => s == "1");
Read more about Find all here.
UPDATE:
Lambda-expression is a shorter way to represent anonymous methods. So you can use them like that:
List<Employee> employees = new List<Employee>()
{
new Employee(){
Id = 111,
Name = "Eric Trump",
Gender = "Male",
Salary = 900000
},
new Employee(){
Id = 112,
Name = "Ayo",
Gender = "Female",
Salary = 8900
}
};
var findAll = employees.FindAll(s => s.Id == 111);
var findLast = employees.FindLast(s => s.Id == 111);
var find = employees.Find(s => s.Id == 111);

Linq group by and then get only one of the items

I have the following class which is used to assign tasks to employees.
public class TaskDetails
{
public int TaskGroupId { get; set; }
public int TaskId { get; set; }
public string AssignedTo { get; set; }
public string TaskName { get; set; }
}
Typically we get a list of task groups and who are they assigned to like the following, each task is grouped under TaskGroupId and under each group for each task we have a specific taskid and who is responsible for it.
List<TaskDetails> tasks = new List<TaskDetails>
{
new TaskDetails
{
AssignedTo = "JOHN",
TaskGroupId = 100,
TaskId = 1,
TaskName = "FA"
},
new TaskDetails
{
AssignedTo = "TOM",
TaskGroupId = 100,
TaskId = 1,
TaskName = "FA"
},
new TaskDetails
{
AssignedTo = "JOHN",
TaskGroupId = 100,
TaskId = 2,
TaskName = "GH"
},
new TaskDetails
{
AssignedTo = "TOM",
TaskGroupId = 100,
TaskId = 2,
TaskName = "GH"
},
new TaskDetails
{
AssignedTo = "JOHN",
TaskGroupId = 99,
TaskId = 1,
TaskName = "XY"
},
new TaskDetails
{
AssignedTo = "TOM",
TaskGroupId = 99,
TaskId = 1,
TaskName = "XY"
},
new TaskDetails
{
AssignedTo = "JOHN",
TaskGroupId = 99,
TaskId = 2,
TaskName = "YX"
},
new TaskDetails
{
AssignedTo = "TOM",
TaskGroupId = 99,
TaskId = 2,
TaskName = "YX"
}
};
What I am trying to do is to group each task by TaskGroupId and AssignedTo, however if a task is assigned to more than one person I only need to retrieve one of them back, in the above example, task 1 is assigned to John and Tom but I only need one of them, it does not matter which one (it could be Tom or John). I have tried the following but it is retrieving 4 results and both John and Tom as seen in the screenshot. I could remove the x.AssignedTo from the GroupBy statement which gives me 2 results but then the tasks are then repeated in TaskLegs section, so not useful either.
var result = tasks
.GroupBy(x => new {
x.TaskGroupId,
x.AssignedTo })
.Select(group => new {
GroupDetails = group.Key,
TaskLegs = group
.OrderBy(x => x.TaskId)
.ToList() })
.ToList();
Is there anyway of grouping the results in a way so I can only retrieve one of the results from the grouped resultset? Based on the above example I am trying to get 2 results, one for task group 100 and one for task group 99.
Thanks
If you only want one item per TaskGroupId then you only group by that field. Grouping by TaskGroupId and AssignedTo means that you will get one group for each combination of the two which is why you are getting four items.
So your query must start:
tasks.GroupBy (x => x.TaskGroupId)
You then have two groups (for TaskGroupId 99 and 100).
You then need to select the data into the form you want. I'm a little unclear on what form this data should take. Should it just have one task under each group?
If so something like:
.Select(group => new { TaskGroupId = group.Key, TaskLegs = group.OrderBy(x => x.TaskId).First() }).
If it should have each distinct task in there then you will need to do some more grouping:
.Select(group => new {
TaskGroupId = group.Key,
TaskLegs = group
.GroupBy(x=>x.TaskId)
.Select(y => y.First())
.OrderBy(y=>y.TaskId)
})
This will give you two items, one for each taskgroup. Each of those items will have two items in the TaskLegs, for TaskIds 1 and 2.
Bonus thought:
If you wanted to list all people assigned to a task you could change your definition of TaskLegs to:
TaskLegs = group
.GroupBy(x=>x.TaskId)
.Select(y => new {
TaskId = y.Key,
AssignedTo = y.Select(z => z.AssignedTo)
})
.OrderBy(y=>y.TaskId)
Try this..
var result = tasks.GroupBy(x => new x.TaskGroupId) .Select(group => new { GroupDetails = group.Key, TaskLegs = group.Take(1) }) .ToList();

How to return Distinct Row using LINQ

I have two rows which have all the data same except one column.
I want to show only one row on the UI but one row which has different data should be shown as comma seperated values.
Sample Data
PricingID Name Age Group
1 abc 56 P1
1 abc 56 P2
Output should be :
PricingID Name Age Group
1 abc 56 P1,P2
I am using this approach but it is not working , it gives me two rows only but data i am able to concatenate with comma.
List<PricingDetailExtended> pricingDetailExtendeds = _storedProcedures.GetPricingAssignment(pricingScenarioName, regionCode, productCode, stateCode, UserId, PricingId).ToList();
var pricngtemp = pricingDetailExtendeds.Select(e => new
{
PricingID = e.PricingID,
OpportunityID = e.OpportunityID,
ProductName = e.ProductName,
ProductCD = e.ProductCD
});
pricingDetailExtendeds.ForEach(e=>
{
e.ProductCD = string.Join(",",string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductCD).ToArray())).Split(',').Distinct().ToArray());
e.OpportunityID =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.OpportunityID).ToArray())).Split(',').Distinct().ToArray());
e.ProductName =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductName).ToArray())).Split(',').Distinct().ToArray());
}
);
// pricingDetailExtendeds = GetUniquePricingList(pricingDetailExtendeds);
return pricingDetailExtendeds.Distinct().AsEnumerable();
Any body can suggest me better approach and how to fix this issue ?
Any help is appreciated.
You want to use the GroupBy linq function.
I then use the String.Join function to make the groups comma seperated.
So something like this:
var pricingDetailExtendeds = new[]
{
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P1"
},
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P2"
}
};
var pricngtemp =
pricingDetailExtendeds.GroupBy(pde => new {pde.PricingID, pde.Name, pde.Age})
.Select(g => new {g.Key, TheGroups = String.Join(",", g.Select(s => s.Group))}).ToList();
You can easily extrapolate this to the other fields.
To return the PricingDetailExtended, the just create it in the select. So something like this
.Select(g => new PricingDetailExtended {
PricingID = g.Key.PricingId,
TheGroups = String.Join(",", g.Select(s => s.Group))
}).ToList();
You won't have the field TheGroups though, so just replace that field with the proper one.
An example of what I was describing in my comment would be something along the lines of the following. I would expect this to be moved into a helper function.
List<PriceDetail> list = new List<PriceDetail>
{
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P1"},
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P2"},
new PriceDetail {Id = 2, Age = 56, Name = "abc", group = "P1"}
};
Dictionary<PriceDetailKey, StringBuilder> group = new Dictionary<PriceDetailKey, StringBuilder>();
for (int i = 0; i < list.Count; ++i)
{
var key = new PriceDetailKey { Id = list[i].Id, Age = list[i].Age, Name = list[i].Name };
if (group.ContainsKey(key))
{
group[key].Append(",");
group[key].Append(list[i].group);
}
else
{
group[key] = new StringBuilder();
group[key].Append(list[i].group);
}
}
List<PriceDetail> retList = new List<PriceDetail>();
foreach (KeyValuePair<PriceDetailKey, StringBuilder> kvp in group)
{
retList.Add(new PriceDetail{Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
}
you could even convert the final loop into a LINQ expression like:
group.Select(kvp => new PriceDetail {Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
Its worth noting you could do something similar without the overhead of constructing new objects if, for example, you wrote a custom equality comparer and used a list instead of dictionary. The upside of that is that when you were finished, it would be your return value without having to do another iteration.
There are several different ways to get the results. You could even do the grouping in SQL.

Using List<Person> Distinct() to return 2 values

I have a Person class, with Name and AreaID properties.
public class Person
{
public string Name;
public int AreaID;
// snip
}
I have a List<Person> with the potential for hundreds of Person objects in the list.
e.g., 100 Persons with AreaID = 1 and 100 Persons with AreaID = 2
I want to return distinct list of AreaID's and how many Persons have that AreaID.
For example,
AreaID = 1 Persons = 100
AreaID = 2 Persons = 100
Use the GroupBy method.
var list = ...list of Persons...
var areas = list.GroupBy( p => p.AreaID )
.Select( g => new {
AreaID = g.Key,
Count = g.Count()
});
Looks like you want to group by area ID then:
var groups = from person in persons
group 1 by person.AreaID into area
select new { AreaID = area.Key, Persons = area.Count() };
I'm using "group 1" to indicate that I really don't care about the data within each group - only the count and the key.
This is inefficient in that it has to buffer all the results for the sake of grouping - you make well be able to use Reactive LINQ in .NET 4.0 to do this more efficiently, or you could certainly use Push LINQ if you wanted to. Then again, for relatively small datasets it probably doesn't matter :)
Surprisingly nobody advised to override Equals and GetHashCode. If you do so you can do folowing:
List<Person> unique = personList.Distinct();
Or even
List<Person> areaGroup = personList.GroupBy(p => p.AreaID);
List<Person> area1Count = personList.Where(p => p.AreaID == 1).Count();
This gives you more flexibility, - no need in useless anonymous class.
return list.GroupBy(p => p.AreaID)
.Select(g => new { AreaID = g.Key, People = g.Count() });
you could use list.GroupBy(x => x.AreaID);
You can try this:
var groups = from person in list
group person by person.AreaID into areaGroup
select new {
AreaID = areaGroup.Key,
Count = areaGroup.Count()
};
var people = new List<Person>();
var q = from p in people
group p by p.AreaId into g
select new { Id = g.Key, Total = g.Count() };
people.Add(new Person { AreaId = 1, Name = "Alex" });
people.Add(new Person { AreaId = 1, Name = "Alex" });
people.Add(new Person { AreaId = 2, Name = "Alex" });
people.Add(new Person { AreaId = 3, Name = "Alex" });
people.Add(new Person { AreaId = 3, Name = "Alex" });
people.Add(new Person { AreaId = 4, Name = "Alex" });
people.Add(new Person { AreaId = 2, Name = "Alex" });
people.Add(new Person { AreaId = 4, Name = "Alex" });
people.Add(new Person { AreaId = 1, Name = "Alex" });
foreach (var item in q)
{
Console.WriteLine("AreaId: {0}, Total: {1}",item.Id,item.Total);
}
Something like this, perhaps ?
List<Person> persons = new List<Person> ();
persons.Add (new Person (1, "test1"));
persons.Add (new Person (1, "test2"));
persons.Add (new Person (2, "test3"));
var results =
persons.GroupBy (p => p.AreaId);
foreach( var r in results )
{
Console.WriteLine (String.Format ("Area Id: {0} - Number of members: {1}", r.Key, r.Count ()));
}
Console.ReadLine ();
Instead of distinct, use GroupBy, or the more succinct LINQ statement:
var results = from p in PersonList
group p by p.AreaID into g
select new { AreaID=g.Key, Count=g.Count() };
foreach(var item in results)
Console.WriteLine("There were {0} items in Area {1}", item.Count, item.AreaID);
ToLookup() will do what you want.

Categories