GroupBy in Enumerable method - c#

I want to make a list of Report class from persons list with group by
class Report
{
public string Country { get; set; }
public double SumAge { get; set; }
}
class Person
{
public int Age { get; set; }
public string Name { get; set; }
public string Country { get; set; }
}
var list = new List<Person>
{
new Person {Age = 35, Country = "Spain", Name = "John"},
new Person {Age = 45, Country = "Spain", Name = "Alex"},
new Person {Age = 55, Country = "Hungary", Name = "Bob"},
new Person {Age = 23, Country = "Spain", Name = "Eve"},
new Person {Age = 39, Country = "India", Name = "Rose"},
new Person {Age = 25, Country = "India", Name = "Peter"},
new Person {Age = 51, Country = "Hungary", Name = "Rob"},
new Person {Age = 23, Country = "India", Name = "Jeff"},
};
list.GroupBy(p => p.Country, p => p)
.Select(p => new Report
{
Country = p.Key,
SumAge = p.Sum() //????
});
There is something wrong in select statement, how can I count a sum of ages?

You need to specify which property to sum:
var res = list.GroupBy(p => p.Country, p => p)
.Select(p => new Report
{
Country = p.Key,
SumAge = p.Sum(x => x.Age)
});

Related

Is there a way in C# to use EXCEPT to compare 2 lists when one doesn't have Id yet?

I get listA from the database. I get listB from a file that the end user uploads, which I convert to the right class in a list. For the example I give you a list that came from the database and one that the user uploaded. You can find samples of these below.
I need to check if listB is in listA, but when I use Except I get the entire list because Id isn't in listB (auto numeration in database). I have a solution for now, but is there a better way to do this?
What I have tried so far:
List<CustomerGroup> listC = listA.Except(listB).ToList();
//Returns listA in listC because Id isn't the same
This is what I am using now, but it seems so redundant.
foreach (CustomerGroup itemToCheck in listB)
{
var foundItem = listA.Find(dbItem => dbItem.FirstName== itemToCheck.FirstName &&
dbItem.FamilyName== itemToCheck.FamilyName &&
dbItem.Quantity == itemToCheck.Quantity &&
dbItem.Discount == itemToCheck.Discount);
if(foundItem != null)
{
listA.Remove(foundItem);
listB.Remove(itemToCheck);
}
}
foreach (CustomerGroup itemToCheck in listB)
{
// other code to check here
}
List<CustomerGroup> listA = new List<CustomerGroup>(){
new CustomerGroup {Id = 1, FirstName = "Anna", FamilyName = "Shrek", Quantity = 5, Discount = 10},
new CustomerGroup {Id = 2, FirstName = "Elsa", FamilyName = "Fiona", Quantity = 5, Discount = 10},
new CustomerGroup {Id = 3, FirstName = "Olaf", FamilyName = "Donkey", Quantity = 5, Discount = 10},
new CustomerGroup {Id = 4, FirstName = "Sven", FamilyName = "Dragon", Quantity = 5, Discount = 5},
new CustomerGroup {Id = 5, FirstName = "Kristoff", FamilyName = "Puss", Quantity = 5, Discount = 10},
new CustomerGroup {Id = 6, FirstName = "Sven", FamilyName = "Dragon", Quantity = 10, Discount = 15},
new CustomerGroup {Id = 7, FirstName = "Kristoff", FamilyName = "Puss", Quantity = 10, Discount = 30}
};
List<CustomerGroup> listB = new List<CustomerGroup>(){
new CustomerGroup { FirstName = "Anna", FamilyName = "Shrek", Quantity = 5, Discount = 10},
new CustomerGroup { FirstName = "Elsa", FamilyName = "Fiona", Quantity = 5, Discount = 8},
new CustomerGroup { FirstName = "Sven", FamilyName = "Dragon", Quantity = 5, Discount = 5},
new CustomerGroup { FirstName = "Kristoff", FamilyName = "Puss", Quantity = 5, Discount = 10},
new CustomerGroup { FirstName = "Sven", FamilyName = "Dragon", Quantity = 10, Discount = 15},
new CustomerGroup { FirstName = "Kristoff", FamilyName = "Puss", Quantity = 10, Discount = 30},
new CustomerGroup { FirstName = "Hans", FamilyName = "Farquaad", Quantity = 20, Discount = 40}
};
public class CustomerGroup{
public int Id {get; set;}
public string FirstName {get; set;}
public string FamilyName {get; set;}
public int Quantity{get; set;}
public int Discount {get; set;}
}
Implement IEqualityComparer<> for class CustomerGroup and then use Except(),
like,
public class CompareCustomerGroup : IEqualityComparer<CustomerGroup>
{
public bool Equals(CustomerGroup dbCustomer, CustomerGroup uploadedCustomer)
{
if(dbCustomer == null || uploadedCustomer == null)
return false;
else
return dbCustomer.FirstName == uploadCustomer.FirstName &&
dbCustomer.FamilyName == uploadCustomer.FamilyName &&
dbCustomer.Quantity == uploadCustomer.Quantity &&
dbCustomer.Discount == uploadCustomer.Discount;
}
public int GetHashCode(CustomerGroup customGroup)
{
return HashCode.Combine(customGroup.FirstName, customGroup.FamilyName , customGroup.Quantity, customGroup.Discount);
}
}
Now try Except(),
List<CustomerGroup> listC = listA.Except(listB, new CompareCustomerGroup()).ToList();
You can use your own IEqualityComparer, that ignores the ID property:
class MyEqualityComparer : IEqualityComparer<CustomerGroup>
{
public bool Equals(CustomerGroup x, CustomerGroup y)
{
if(x is null) return y is null;
if(y is null) return false;
return x.FirstName == y.FirstName && x.FamilyName == y.FamilyName && x.Quantity == y.Quantity && x.Discount == y.Discount;
}
public int GetHashCode(CustomerGroup codeh)
{
return 0;
}
}
Pass this to Except:
List<CustomerGroup> listC = listA.Except(listB, new MyEqualityComparer()).ToList();
Online demo: https://dotnetfiddle.net/gogy9g

How do I remove objects with a combination of duplicate of two properties from List with linq?

Same question as this one but I need to remove objects with a combination of duplicate of two properties from List.
There is a set of objects, objects have age and Name:
21 Carl
23 Vladimir
25 Bob
21 Olivia
21 Carl
30 Jacob
23 Vladimir
Output list should contain:
21 Carl
23 Vladimir
25 Bob
21 Olivia
30 Jacob
How do I remove it?
Try this:
public class KeyValueClass
{
public int Age { get; set; }
public string Name { get; set; }
}
private void DoTheJob()
{
var myList = new List<KeyValueClass>
{
new KeyValueClass {Age = 21, Name = "Carl"},
new KeyValueClass {Age = 23, Name = "Vladimir"},
new KeyValueClass {Age = 25, Name = "Bob"},
new KeyValueClass {Age = 21, Name = "Olivia"},
new KeyValueClass {Age = 21, Name = "Carl"},
new KeyValueClass {Age = 30, Name = "Jacob"},
new KeyValueClass {Age = 23, Name = "Vladimir"},
};
var myDistinctList = myList.GroupBy(x => new { x.Age, x.Name })
.Select(c => c.First()).ToList();
}
You can use Distinct() from the Linq namespace and a IEqualityComparer:
class Program
{
static void Main(string[] args)
{
List<KeyValueClass> myList = new List<KeyValueClass>
{
new KeyValueClass {Age = 21, Name = "Carl"},
new KeyValueClass {Age = 23, Name = "Vladimir"},
new KeyValueClass {Age = 25, Name = "Bob"},
new KeyValueClass {Age = 21, Name = "Olivia"},
new KeyValueClass {Age = 21, Name = "Carl"},
new KeyValueClass {Age = 30, Name = "Jacob"},
new KeyValueClass {Age = 23, Name = "Vladimir"},
};
var myDistincList = myList.Distinct(new KeyValueEqualityComparer());
foreach (var item in myDistincList) { Console.WriteLine("Age: {0}, Name:{1}", item.Age, item.Name); }
Console.ReadKey();
}
}
public class KeyValueClass
{
public int Age { get; set; }
public string Name { get; set; }
}
class KeyValueEqualityComparer : IEqualityComparer<KeyValueClass>
{
public bool Equals(KeyValueClass x, KeyValueClass y)
{
if (x == null || y == null) return false;
if (x.Age == y.Age && x.Name.Equals(y.Name)) return true;
return false;
}
public int GetHashCode(KeyValueClass obj)
{
return (obj.Age + obj.Name).GetHashCode() + 387;
}
}
Just use an ISet to avoid the overhead and slow performance of a List:
public class Person : IEquatable<Person>
{
public int Age { get; private set;}
public string Name { get; private set;}
public bool override Equals(Person other){
return other.Age == Age && other.Name.Equals(Name);
}
public override int GetHashCode(){
return Age.GetHashCode() ^ Name.GetHashCode();
}
}
private IEnumerable<Person> MakeUniqueList(IEnumerable<Person> input){
return new HashSet<Person>(input);
}
In order to actually remove, which will be quite slow performance wise (but it will save on memory usage), use List.Remove(T)
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.remove?view=netframework-4.7.2

How to Use Effeciently Where Clause or Select in LINQ Parallel in Large Dataset

I'm having approx 250,000 records as marked as Boss, each Boss has 2 to 10 Staff. Daily I need to get the details of the Staff. Approx there are 1,000,000 staff. I'm using Linq to get the Unique list of Staff who are worked in daily basis. Consider the following C# LINQ and Models
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"},
}
},
..... ~ 250,000 Records ......
};
List<Person> staffList = 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();
}
public class Person
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
}
public class Boss
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public List<Person> Employees { get; set; }
}
In the above LINQ I'm getting the List of Distinct Employees or Staff, the list contains more than 1,000,000 records. From the Obtained List I need to search "Raj"
staffList.Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant()));
For this operation, it took more than 3 to 5 minutes to get the result.
How could I make it more efficient. Kindly assist me...
If you change Boss to inherit from Person ( public class Boss : Person ) not only do you not need to duplicate your properties in Person and Boss, you don't have to create all new Person instances for each Boss, because a Boss is already a Person:
IEnumerable<Person> staff = BossList
.Concat(BossList
.SelectMany(x => x.Employees)
)
.DistinctBy(p => p.EmpId)
.ToList()
Where DistinctByis defined as
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
var seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
Also, in your comparison, you're converting every Name to lowercase and doing the comparison - that's a lot of string creation that you don't need. Instead, try something like
staffList.Where(m => m.Name.Equals("Raj", StringComparison.InvariantCultureIgnoreCase));
Also, be aware that your use of Contains would also match names like Rajamussenand mirajii - possibly not what you were expecting.
Would it work for you to change staffList to a Dictionary? A better search algorithm as those from Dictionary and SortedList would get you the most improvement.
I've tested the code below and it runs in just a few seconds.
private static void Main()
{
List<Boss> BossList = new List<Boss>();
var b1 = 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"},
}
};
var b2 = 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"},
}
};
Random r = new Random();
var genders = new [] {"Male", "Female"};
for (int i = 0; i < 1500000; i++)
{
b1.Employees.Add(new Person { Name = "Name" + i, Department = "Department" + i, Gender = genders[r.Next(0, 1)], EmpID = 200 + i });
b2.Employees.Add(new Person { Name = "Nam" + i, Department = "Department" + i, Gender = genders[r.Next(0, 1)], EmpID = 1000201 + i });
}
BossList.Add(b1);
BossList.Add(b2);
Stopwatch sw = new Stopwatch();
sw.Start();
var emps = 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());
var staffList = emps.ToList();
var staffDict = emps.ToDictionary(p => p.Name.ToLowerInvariant() + p.EmpID);
var staffSortedList = new SortedList<string, Person>(staffDict);
Console.WriteLine("Time to load staffList = " + sw.ElapsedMilliseconds + "ms");
var rajKeyText = "Raj".ToLowerInvariant();
sw.Reset();
sw.Start();
var rajs1 = staffList.AsParallel().Where(p => p.Name.ToLowerInvariant().Contains(rajKeyText)).ToList();
Console.WriteLine("Time to find Raj = " + sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
var rajs2 = staffDict.AsParallel().Where(kvp => kvp.Key.Contains(rajKeyText)).ToList();
Console.WriteLine("Time to find Raj = " + sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
var rajs3 = staffSortedList.AsParallel().Where(kvp => kvp.Key.Contains(rajKeyText)).ToList();
Console.WriteLine("Time to find Raj = " + sw.ElapsedMilliseconds + "ms");
Console.ReadLine();
}
public class Person
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
}
public class Boss
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public List<Person> Employees { get; set; }
}
}
Output1:
Output2 (using .AsParallel() on searches):
In other words, if you can't use some faster data structure, up can speed your search up just by changing form
staffList.Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant()));
to
staffList.AsParallel().Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant()));

Order GroupBy Asc based on Two more Properties using LINQ C#

My GroupBy is performing well. I'm getting the Output
I need to Sort the Group Names
The Brown Color Block, represents the Group.
The Red Color Box within the Brown Color Block, represents the Manager
Peter Block (Brown Box) Should Come first
Raj Block (Brown Box) Should Come Second
Sunny Block (Brown Box) Should Come Third
Each Block Should Group By Boss(Manager) and Assistant (Boss don't have the
SID). After GroupBy the Name should be in Sorted Order, within the Group
the Assistant Names are also in the Sorted Order.
The Model Classes:
public class Person
{
public int ID { get; set; }
public int SID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public string Role { get; set; }
}
public class Boss
{
public int ID { get; set; }
public int SID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public string Role { get; set; }
public List<Person> Employees { get; set; }
}
The Main Functionality Source Code:
void Main()
{
List<Boss> BossList = new List<Boss>()
{
new Boss()
{
ID = 101,
Name = "Sunny",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 101, SID = 102, Name = "Peter", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 101, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
}
},
new Boss()
{
ID = 104,
Name = "Raj",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 104, SID = 105, Name = "Kaliya", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 104, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
},
},
new Boss()
{
ID = 102,
Name = "Peter",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 102, SID = 105, Name = "Kaliya", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 102, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
}
}
};
List<Person> EmpList = BossList.SelectMany(i =>
new[] {
new Person()
{
ID = i.ID,
SID = i.SID,
Name = i.Name,
Gender = i.Gender,
Department = i.Department,
Role = i.Role
}
}.Concat(i.Employees)
).ToList().GroupBy(s => s.ID).SelectMany(h => h.GroupBy(g => g.SID).SelectMany(u => u.OrderBy(k=> k.Name))).ToList();
}
You can do by adding the ThenBy extension method after the Order by to apply the secondary sort. In fact, the ThenBy can be called multiple times for sorting on multiple property. I have modified the last line of your code to show how you can achieve this.
).ToList().GroupBy(s => s.ID).SelectMany(h => h.GroupBy(g => g.SID).SelectMany(u => u.OrderBy(k=> k.Name).ThenBy(l => l.<<secondproperty>>))).ToList();
The datastructure already establishes the groups. There is no need to re-group.
List<Person> result = (
from boss in BossList
order by boss.Name
let orderedEmployees = boss.Employees.OrderBy(emp => emp.Name)
let bossPerson = new Person(boss)
let people = new List<Person>() { bossPerson }.Concat(orderedEmployees)
from person in people
select person).ToList();
If you prefer the lambda syntax:
List<Person> result = BossList
.OrderBy(boss => boss.Name)
.SelectMany(boss => {
IEnumerable<Person> orderedEmployees = boss.Employees.OrderBy(emp => emp.Name);
Person bossPerson = new Person(boss);
return new List<Person>() { bossPerson }.Concat(orderedEmployees);
})
.ToList();

Group List<Person> by properties and get number of times grouped

Lets say I have a class called Person:
public class Person
{
public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And a list of persons:
Person { Age = 20, FirstName = "John", LastName = "Joe" }
Person { Age = 20, FirstName = "John", LastName = "Joe" }
Person { Age = 10, FirstName = "James", LastName = "Dokes" }
What I want to have is a (new or old with a new property) list that groups the person by age, first name and last name AND I also want to know how many times that object has been grouped.
So the result of the above would be:
Person { Age = 20, FirstName = "John", LastName = "Joe", Count = 2 }
Person { Age = 10, FirstName = "James", LastName = "Dokes", Count = 1 }
Simple:
people.GroupBy(x => new { x.Age, x.FirstName, x.LastName })
.Select(x => new { x.Key.Age, x.Key.FirstName, x.Key.LastName, Count = x.Count() });
Just before I posted I saw answer already from JustAnotherUserYouMayKnow, so was going to cancel, but am posting anyway just to show same result using only the GroupBy, with the resultSelector parameter (instead of separate projection)
var personList = new List<Person> {
new Person { Age = 20, FirstName = "John", LastName = "Joe" },
new Person { Age = 20, FirstName = "John", LastName = "Joe" },
new Person { Age = 10, FirstName = "James", LastName = "Dokes"}};
var results = personList.GroupBy(per => new { per.Age, per.FirstName, per.LastName },
(key, items) => new { key.Age, key.FirstName, key.LastName, Count = items.Count() } );
Non-proc alternative:
List<Person> ls = new List<Person>();
ls.Add (new Person() { Age = 20, FirstName = "John", LastName = "Joe" });
ls.Add(new Person() { Age = 20, FirstName = "John", LastName = "Joe" });
ls.Add(new Person() { Age = 10, FirstName = "James", LastName = "Dokes" });
var result = (from cur in ls
group cur by new { Age = cur.Age, Name = cur.FirstName, LastName = cur.LastName }
into tmpResult
select new { tmpResult.Key.Age,tmpResult.Key.LastName,tmpResult.Key.Name,Count = tmpResult.Count()});

Categories