How to use LINQ to group objects in two groups - c#

Lets say that I have the class Invoice:
public class Invoice
{
public int PartNumber { get; set; }
public string PartDescription { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
and then I have an array of its objects in the variable arrayOfInvoices .
If I had to Group invoices in two groups- invoices with Unit price below 12 and invoices with unit price above or equal to 12 and display details about invoices in each group sorted in ascending order of the price, how could I do that ?

You can simply do something like this:
var results =
from inv in arrayOfInvoices
orderby inv.Price
group inv by inv.Price < 12;
Or if you prefer fluent syntax:
var results = arrayOfInvoices.OrderBy(inv => inv.Price)
.GroupBy(inv => inv.Price < 12);
To group the invoices into three or more 'buckets', you can use BinarySearch:
var priceBoundaries = new[] { 12m, 20m };
var results =
from inv in arrayOfInvoices
orderby inv.Price
let index = Array.BinarySearch(priceBoundaries, inv.Price)
group inv by (index < 0 ? ~index : index + 1);
Or use side effects, like this:
var priceBoundaries = new[] { 12m, 20m, Decimal.MaxValue }; // Note the addition of MaxValue
var i = 0;
var results =
from inv in arrayOfInvoices
orderby inv.Price
group inv by (inv.Price < priceBoundaries[i] ? i : ++i);
This is generally bad practice, but should perform better than the BinarySearch method above.

If using group function is a pain (sometimes it gets annoying), then you can also use "Where"
var invoices = new List<Invoice> ();
var group1= invoices.Where(i=> i.Price<12).Orderby(i=> i.Price).ToList();
var group2= invoices.Where(i=> i.Price>=12).Orderby(i=> i.Price).ToList();

You can encapsulate the concept of the range in a class:
private class PriceRange<T> : IEquatable<PriceRange<T>>
{
public T Min { get; set; }
public T Max { get; set; }
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + Min.GetHashCode();
hash = hash * 31 + Max.GetHashCode();
return hash;
}
}
public override bool Equals(object obj)
{
return Equals(obj as PriceRange<T>);
}
public bool Equals(PriceRange<T> other)
{
if (other == null) return false;
if (!Min.Equals(other.Min)) return false;
if (!Max.Equals(other.Max)) return false;
return true;
}
}
Then use a map function to map the prices of each of your invoices to the appropriate range:
private class PriceRangeFactory<T>
{
public PriceRangeFactory(T[] rangeCutoffs)
{
_RangeCutoffs = rangeCutoffs;
}
private T[] _RangeCutoffs;
public PriceRange<T> Map(T price)
{
var index = Array.BinarySearch(_RangeCutoffs, price);
// Assume that the _RangeCutoffs that we have fully cover all possible values for T.
if (index < 0) index = ~index;
return new PriceRange<T>() { Min = _RangeCutoffs[index - 1], Max = _RangeCutoffs[index] };
}
}
Then use GroupBy with that map function:
var rangeFactory = new PriceRangeFactory<decimal>(new decimal[] { decimal.MinValue, 12, 20, decimal.MaxValue });
var grouped = foos.GroupBy(a => rangeFactory.Map(a.Price));
You get a list of IGroupings each keyed by a range that you specified, with the appropriate objects that fit into that range attached.
Now, obviously the code above is not quite production level, but it should be enough to get you started. As for why its not production level:
There's no check to assert that the ranges supplied to the PriceRangeFactory actually do fully cover all possible values.
It always assumes that the range is described as > X and <= Y.
There's no checking around Range.Min < Range.Max.
There's no assert that the list of range cutoffs supplied to the PriceRangeFactory are ordered correctly (required for BinarySearch).
There are bound to be some edge cases I haven't covered.

Related

changing the sorted column value change the sorting list , while wants to use the ThenByDescending on first ordered list

I have following code, In which there are list of students , and I want to sort the students first by value column which contains decimal values and after that I want to sort the already sorted list with same column but with different values . Just for understanding , I changed values using foreach loop in the below example.
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var students = new List<Student>()
{
new Student() { StudentId=1,Name = "Alice", Appd = 10, Value = 3.5 },
new Student() { StudentId=2,Name = "Bob", Appd = 10, Value = 3.7 },
new Student() { StudentId=3,Name = "Raul", Appd = 10, Value = 0.1 },
new Student() { StudentId=4,Name = "Charlie", Appd = 0, Value = 3.6 },
new Student() { StudentId=5,Name = "Dave", Appd = 0, Value = 3.9 },
new Student() { StudentId=6,Name = "Emma", Appd = 0, Value = 3.8 }
};
var orderedFields = students.OrderByDescending(x => x.Value);//5,6,2,4,1,3
foreach ( Student s in orderedFields )
{
s.Value = 120;
}
orderedFields = orderedFields.ThenByDescending(x => x.Value);
var newlist1 = orderedFields.Select(X => X.StudentId).ToList();
}
}
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public int Appd { get; set; }
public double Value { get; set; }
}
}
but as soon I change the Value column values it start to change the order of items in list , and if I take this in another list then I will be not able to use the ThenByDescending feature results.
This is sample code to simplify the problem , in real example these columns name come from Database and based on those columns I want to sort the list, first by first column and then by another columns mentioned. For example in MySQL it will be something like this order by col1 desc, col2 desc.
As everybody is comments is discussing the clone and then sort again the list . so here is issue with that approach.
#1. First Set sorting values in Value column for each student :
Value column first contains for each student either 1 or 0 depending on its enrollment date from the cut off date.
#2 Then on same Value column there is CGPA for each student so student should be sorted based on that.
In short all students who apply before cut off date should appear
first and then sort by descending CGPA and then students who apply
after cut off date but those also should come in descending order of
CGPA.
problem is I have only one column here for values, on which need to be sort.
Second edit :
if (_trackConfigManager.RankDependentOnFields.Any())
{
infoFields.ForEach(x => x.PropertyToCompare = _trackConfigManager.RankDependentOnFields.FirstOrDefault().FieldId);
//Order the table withrespect to the firstfield
var orderedFields = infoFields.OrderByDescending(x => x.Value);
//Then skip the first element and order the rest of the fields by descending.
foreach (var field in __trackConfigManager.RankDependentOnFields.RemoveFirst())
{
infoFields.ForEach(x => x.PropertyToCompare = field.FieldId);
orderedFields = orderedFields.ThenByDescending(x => x.Value);
}
//Format a studentId, Rank dictionary from the above orderded table
int rank = 1 + GetMaxRank(programId, statusId);
}
and RankAggregate class as follow :
public class RankAggregate
{
public student_highschool_info HsInfoObj { get; set; }
public student_interview_info IntInfoObj { get; set; }
public student StuObj { get; set; }
private student_program SpObj { get; set; }
public string PropertyToCompare { get; set; }
public bool IsDateTimeField { get; set; }
public long StudentId { get; set; }
public int Choice { get; set; }
public double Value
{
get
{
var tokens = PropertyToCompare.Split(new char[] {':'});
if (tokens.Count() > 1)
{
PropertyToCompare = (Choice == 1)
? "student_interview_FirstPrgTotalScore"
: (Choice == 2) ? "student_interview_SecondPrgTotalScore" : "dummy";
}
var fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student_highschool_info), PropertyToCompare);
if (fldInfo != null)
{
if (HsInfoObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_highschool_info),
PropertyToCompare, HsInfoObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_highschool_info),
PropertyToCompare, HsInfoObj) ?? 0;
return Convert.ToDouble(val1);
}
}
fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student_interview_info), PropertyToCompare);
if (fldInfo != null)
{
if (IntInfoObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_interview_info),
PropertyToCompare, IntInfoObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_interview_info),
PropertyToCompare, this.IntInfoObj) ?? 0;
return Convert.ToDouble(val1);
}
}
fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student), PropertyToCompare);
if (fldInfo != null)
{
if (StuObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student),
PropertyToCompare, StuObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student),
PropertyToCompare, this.StuObj) ?? 0;
return Convert.ToDouble(val1);
}
}
return 0.0;
}
}
public RankAggregate(long studentId, student_highschool_info _hsInfo, student_interview_info _intInfo, student _profileInfo, student_program _spInfo)
{
StudentId = studentId;
HsInfoObj = _hsInfo;
IntInfoObj = _intInfo;
StuObj = _profileInfo;
SpObj = _spInfo;
if (SpObj != null)
{
Choice = SpObj.choice;
}
}
}
Don't know why can't you add another field to the Student class, anyway since you can't do that, you have to fix these values in some places, for example using a tuple:
var studentsWithValues = students.Select(s => (s, s.Value))
.ToList();
Then after changing the values, you can sort the above array:
var orderedFields = studentsWithValues.OrderByDescending(t => t.Value)
.ThenByDescending(t => t.s.Value)
.Select(t => t.s)
Update for uncertain columns
Bind each student object with a list of values:
var studentsWithValues = students.Select(s => new
{
Student = s,
Values = new List<double> { s.Value }
})
.ToList();
After the values are updated, append each value to the binded list:
UpdateValues();
studentsWithValues.ForEach(t => t.Values.Add(t.Student.Value));
Then you can sort these values:
var e = studentsWithValues.OrderByDescending(t => t.Values[0]);
var valueCount = studentsWithValues.First().Values.Count;
for (int i = 1; i < valueCount; i++)
{
int j = i;
e = e.ThenByDescending(t => t.Values[j]);
}
var orderedFields = e.Select(t => t.Student);
Short answer
Use:
var orderedStudents = students
.OrderByDescending(student => student.Value)
.ToList();
foreach (Student student in orderedStudents) etc.
Longer answer
Your orderedFields is not a list, nor a sequence. It is an object that can be enumerated. It has not been enumerated yet. In other words: it is not a Collection<T>, it is an IEnumerable<T>. Usually in descriptions you'll find the phrases: delayed execution or deferred execution.
When you execute foreach (Student s in orderedFields), you start to enumerate the items in students. You don't enumerate the items in the original order, you enumerate them ordered by .Value.
but as soon I change the Value column values it start to change the order of items in list
So, the next time you enumerate orderedFields, the items in students are enumerated again, and ordered again by the changed value of .Value.
If you want to change the source of the items in your LINQ statement, you have to execute the delayed execution by calling one of the LINQ methods that doesn't return IEnumerable<T>, like ToList(), ToArray(), ToDictionary(), but also FirstOrDefault(), Sum(), Count(), Any()
By calling one of the non-delayed methods, the source is enumerated and the result is put in a new object. If you change the items in the new object, and use this new object as source for your next LINQ-statement, then the order of the new object is used, not the order in the original object.
Careful: if you put the references of the original items in the new List, and you change the values, you change the original items. If you don't want that, use a Select(student => new {...}) to put the values in a new object. If you change those values, the original students are not affected.

How to Compare List with Other List in same Collection

Hello I have one List of List Which is dynamic means some times my list contains two lists or some time it will contais three lists like here we can say my List contais three lists.
list1.Add(new Schema() { High = 10, Low = 8, OpenValue = 7, Price = 8.5, Time = DateTime.Today.AddDays(-7), Volume = 234234232 });
list2.Add(new Schema() { High = 10, Low = 8, OpenValue = 7, Price = 8.5, Time = DateTime.Today.AddDays(-6), Volume = 234234232 });
list3.Add(new Schema() { High = 10, Low = 8, OpenValue = 7, Price = 8.5, Time = DateTime.Today.AddDays(-7), Volume = 234234232 });
and I have
List<List<Schema>> llsl = new List<List<Schema>>();
llsl.Add(list1);
llsl.Add(list2);
llsl.Add(list3);
now I want to compare List with each other like First I have to Compare list1 to list2 then list1 to list3 then list2 to list3 then list2 to list1 and so on so can anyone please help me how can I Achive it.
The purpose of doing it I want to final list which contais same no of Items let's us say my list1 Contain date 20 feb same my list3 also Contain a date 20feb but my list2 doesn't contain so I want final list which contains all three list but list2 with null value because it's not contains 20 feb so basically I want to compare dates in lists.
Thanks in Advance.
Your question is not much clear to me but If you wanted to make element null if it does not match with any other element in collection than here is how you go.
override a Equals and Hash method in your class called Schema as follow:
public class Schema
{
public int High { get; set; }
public int Low { get; set; }
public int OpenValue { get; set; }
public double Price { get; set; }
public DateTime Time { get; set; }
public int Volume { get; set; }
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Schema)obj);
}
protected bool Equals(Schema other)
{
//You may would like to compare only Date here.
return High == other.High && Low == other.Low && OpenValue == other.OpenValue && Price.Equals(other.Price) && Time.Equals(other.Time) && Volume == other.Volume;
}
public override int GetHashCode()
{
unchecked
{
int hashCode = High;
hashCode = (hashCode * 397) ^ Low;
hashCode = (hashCode * 397) ^ OpenValue;
hashCode = (hashCode * 397) ^ Price.GetHashCode();
hashCode = (hashCode * 397) ^ Time.GetHashCode();
hashCode = (hashCode * 397) ^ Volume;
return hashCode;
}
}
}
Then create Extension method like follows.(Look at the comments for the logic).
public static class ListExtension
{
public static List<List<T>> MatchList<T>(this List<List<T>> list) where T : class
{
//will contain matched list element index
var matchedList = new List<Tuple<int, int>>();
for (var i = 0; i < list.Count - 1; i++)
{
for (var j = i + 1; j < list.Count; j++)
{
//add element to matchedList if all are equal.
var iElement = list.ElementAt(i);
var jElement = list.ElementAt(j);
if (iElement.Count != jElement.Count) continue;
var flag = !iElement.Where((t, k) => !iElement.ElementAt(k).Equals(jElement.ElementAt(k))).Any();
if (flag)
{
//add element here.
matchedList.Add(new Tuple<int, int>(i, j));
}
}
}
var item1 = matchedList.Select(d => d.Item1).ToList();
var item2 = matchedList.Select(d => d.Item2).ToList();
//distinct elements that matchced perfectly.
var sameGroup = item1.Union(item2);
for (var i = 0; i < list.Count; i++)
{
if (!sameGroup.Contains(i))
{
//make it null where it did not matched as your requirement
list[i] = null;
}
}
//finally return the updated list.
return list;
}
}
Here is working fiddle for you in case you wanted to test and play with it.

Group records stored in one-dimensional array using LINQ

I have a situation, where I get data from the database in such a way, that everything is stored in one-dimensional array.
For example:
User table: UserId, Name
Group table: GroupId, Name
UserGroup table: UserId, GroupId
As a result of joining these tables I obtain array of the following form:
result[0] = "1" // user id
result[1] = "John Doe" // user name
result[2] = "121" // group id
result[3] = "SomeGroup" // group name
result[4] = "1" // user id
result[5] = "John Doe" // user name
result[6] = "2135" // group id
result[7] = "SomeOtherGroup" // group name
I know it's not a good solution of keeping data, but these data are coming to me from some other piece of code which I am not allowed to change, so I have to deal with it.
My questions are:
Is this possible to use LINQ in order to parse this array and place data in my own objects (User class with Groups collection in it).
What is other best way to handle it if not by LINQ?
Pure linq Expression :
int i = 0;
var objects = result.GroupBy(x => Math.Floor(i++ / 4.0))
.Select(g => new { id =g.ElementAt(0), name = g.ElementAt(1), gId= g.ElementAt(2), group = g.ElementAt(3)})
.GroupBy(x=>new {x.id, x.name}, x=>new {x.gId, x.group})
.Select(y=>new {y.Key, groups = y.ToList()});
In the first GroupBy I group results in 4 elements subsets using a floor and a temporary variable.
Then The next Select put the resulting arrays in an anonymous type for better usability in the next steps.
The next GroupBy is used to group the entries by Employee. The Key will be the employee and the values will be the corresponding Groups.
Finaly the lase Selectis used to put the GroupByresult in a better shape. I choose to put the result in an other anonymous type but You could instantiate you custom objects here and put the values in the right fields using curly brace constructor.
If your logic depends on indexes LINQ is is rarely the right tool. It results in less readable, maintainable, efficient and robust code than with plain loops.
I would use something like following to create two dictionaries representing the many to many relation. Note the for-loop which increments by 4 on every iteration since that seems to be the user-group-"package":
var userIdGroups = new Dictionary<int, HashSet<Group>>();
var groupIdUsers = new Dictionary<int, HashSet<User>>();
for(int i = 0; i < result.Length; i += 4)
{
int id;
if(int.TryParse(result[i], out id))
{
string name = result.ElementAtOrDefault(i + 1);
if(name == null)
continue; // end, invalid data
User user = new User{ UserId = id, Name = name };
string groupID = result.ElementAtOrDefault(i + 2);
if(!int.TryParse(groupID, out id))
continue; // end, invalid data
name = result.ElementAtOrDefault(i + 3);
if(name == null)
continue; // end, invalid data
Group group = new Group{ GroupId = id, Name = name };
HashSet<Group> userGroups;
HashSet<User> groupUsers;
if (userIdGroups.TryGetValue(user.UserId, out userGroups))
userGroups.Add(group);
else
userIdGroups.Add(user.UserId, new HashSet<Group>{ group });
if (groupIdUsers.TryGetValue(group.GroupId, out groupUsers))
groupUsers.Add(user);
else
groupIdUsers.Add(group.GroupId, new HashSet<User> { user });
}
}
The result is:
the user-dictionary contains one user with two groups
the group-dictionary contains two groups which map to the same user
You have to override Equals and GetHashCode to compare the ID's:
class User
{
public int UserId { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
User u2 = obj as User;
if (u2 == null) return false;
return UserId == u2.UserId;
}
public override int GetHashCode()
{
return UserId;
}
}
class Group
{
public int GroupId { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
Group g2 = obj as Group;
if (g2 == null) return false;
return GroupId == g2.GroupId;
}
public override int GetHashCode()
{
return GroupId;
}
}
You can do it with the basic structures like loops:
void Main()
{
var result = new string[] {"1","John Doe","2","Joes Group","3","Jack Daniel","4","Jacks Group","5","Foo Bar","6","FooBar Group",};
List<Person> personList = new List<Person>();
List<Group> groupList = new List<Group>();
for(int i = 0; i < result.Length; i+=2)
{
i = i + 2;
//check if group does not already exist
groupList.Add(new Group() {Name = result[i+1]});
}
for(int i = 0; i < result.Length; i+=2)
{
//check if person exists.
//if person only add group to person personList.Where(x => x.Name ==result[i+1])....
personList.Add(new Person() { Id = int.Parse(result[i]),
Name = result[i+1],
Groups = new List<Group>()
{
groupList.FirstOrDefault (l => l.Name == result[i+3])
}
});
i = i+2;
}
personList.Dump();
}
public class Person
{
public Person()
{
Groups = new List<Group>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Group> Groups { get; set; }
}
public class Group
{
public string Name { get; set; }
}
// Define other methods and classes here
Output:
Please take advise: this code does not contain any validation logic, or duplicate checks. You'll have to imlpement this by yourself.
But before you implement something like this, I'd rather change the way you get your data delivered. this way you would deal with the root of your peroblems not with the symptoms.
i think no need to linq
//some class
public class Result
{
public string UserId {get;set;}
public string UserName {get;set;}
public string GroupId {get;set;}
public string GroupName {get;set;}
public string UserGroupUserId {get;set;}
public string UserGroupUserName {get;set;}
public string UserGroupId {get;set;}
public string UserGroupGroupId {get;set;}
}
// your array
private void Form1_Load(object sender, EventArgs e)
{
string[] result = new string[8];
result[0] = "1";
result[1] = "John Doe";
result[2] = "121";
result[3] = "SomeGroup";
result[4] = "1";
result[5] = "John Doe";
result[6] = "2135";
result[7] = "SomeOtherGroup";
Result r = CastResult(result);
}
// simple cast array to some type
public Result CastResult(string[] array)
{
return new Result() { UserId=array[0], UserName=array[1], GroupId=array[2], GroupName=array[3], UserGroupUserId=array[4], UserGroupUserName=array[5] , UserGroupId=array[6], UserGroupGroupId=array[7] };
}

Remove duplicate objects from a list based on a property [duplicate]

This question already has answers here:
Filter out distinct elements with condition
(3 answers)
Closed 8 years ago.
In a list of SchematicElevation objects how can I remove the ones with the same property using LINQ.
public class SchematicElevation
{
public double Elevation { get; set; }
public int ColumnId { get; set; }
}
var se1 = new SchematicElevation { ColumnId = 2, Elevation = 300 };
var se2 = new SchematicElevation { ColumnId = 3, Elevation = 300 };
var se3 = new SchematicElevation { ColumnId = 4, Elevation = 300 };
var se4 = new SchematicElevation { ColumnId = 4, Elevation = 300 };
var se5 = new SchematicElevation { ColumnId = 4, Elevation = 300 };
var SchematicElevations = new List<SchematicElevation> { se1, se2, se3, se4, se5}
The final result would be the list below:
List: {se1, se2, se3}
I would like doing this using LINQ and by using GroupBy.
You should implement equality, either directly on SchematicElevation or via an IEqualityComparer<T>. For example, using a hash algorithm from Jon Skeet:
public class SchematicElevation : IEquatable<SchematicElevation>
{
public double Elevation { get; set; }
public int ColumnId { get; set; }
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + Elevation.GetHashCode();
hash = hash * 23 + ColumnId.GetHashCode();
return hash;
}
}
public override bool Equals(object other)
{
return Equals(other as SchematicElevation);
}
public bool Equals(SchematicElevation other)
{
return other != null && this.Elevation == other.Elevation &&
this.ColumnId == other.ColumnId;
}
}
Then you simply have to do a Distinct() to get the distinct items:
var schematicElevations = new List<SchematicElevation> {se1, se2, se3, se4, se5};
var distinct = schematicElevations.Distinct();
Or, you could do a GroupBy. This makes for shorter code, but unlike the above, it is not reusable or very maintainable. E.g. if you want to change the criteria, you'd have to change this code everywhere it's used, instead of just the SchematicElevation class.
var schematicElevations = new List<SchematicElevation> {se1, se2, se3, se4, se5};
var distinct = schematicElevations.GroupBy(x => new { x.ColumnId, x.Elevation })
.Select(g => g.First());
I would highly recommend implementing IEquatable. Then you can use the method Distinct() that was designed to do what you want (instead of Group By which is not designed to do what you want, but still allows you to get there).
var result = SchematicElevations.Distinct();

Select distinct values from a list using LINQ in C#

I have a collection of Employee
Class Employee
{
empName
empID
empLoc
empPL
empShift
}
My list contains
empName,empID,empLoc,empPL,empShift
E1,1,L1,EPL1,S1
E2,2,L2,EPL2,S2
E3,3,L3,EPL3,S3
E4,4,L1,EPL1,S1
E5,5,L5,EPL5,S5
E6,6,L2,EPL2,S2
I need to take the employees having distinct values
empLoc,empPL,empShift.
Is there is any way to achieve this using LINQ ?
You can use GroupBy with anonymous type, and then get First:
list.GroupBy(e => new {
empLoc = e.empLoc,
empPL = e.empPL,
empShift = e.empShift
})
.Select(g => g.First());
You could implement a custom IEqualityComparer<Employee>:
public class Employee
{
public string empName { get; set; }
public string empID { get; set; }
public string empLoc { get; set; }
public string empPL { get; set; }
public string empShift { get; set; }
public class Comparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
return x.empLoc == y.empLoc
&& x.empPL == y.empPL
&& x.empShift == y.empShift;
}
public int GetHashCode(Employee obj)
{
unchecked // overflow is fine
{
int hash = 17;
hash = hash * 23 + (obj.empLoc ?? "").GetHashCode();
hash = hash * 23 + (obj.empPL ?? "").GetHashCode();
hash = hash * 23 + (obj.empShift ?? "").GetHashCode();
return hash;
}
}
}
}
Now you can use this overload of Enumerable.Distinct:
var distinct = employees.Distinct(new Employee.Comparer());
The less reusable, robust and efficient approach, using an anonymous type:
var distinctKeys = employees.Select(e => new { e.empLoc, e.empPL, e.empShift })
.Distinct();
var joined = from e in employees
join d in distinctKeys
on new { e.empLoc, e.empPL, e.empShift } equals d
select e;
// if you want to replace the original collection
employees = joined.ToList();
You can try with this code
var result = (from item in List
select new
{
EmpLoc = item.empLoc,
EmpPL= item.empPL,
EmpShift= item.empShift
})
.ToList()
.Distinct();
I was curious about which method would be faster:
Using Distinct with a custom IEqualityComparer or
Using the GroupBy method described by Cuong Le.
I found that depending on the size of the input data and the number of groups, the Distinct method can be a lot more performant. (as the number of groups tends towards the number of elements in the list, distinct runs faster).
Code runs in LinqPad!
void Main()
{
List<C> cs = new List<C>();
foreach(var i in Enumerable.Range(0,Int16.MaxValue*1000))
{
int modValue = Int16.MaxValue; //vary this value to see how the size of groups changes performance characteristics. Try 1, 5, 10, and very large numbers
int j = i%modValue;
cs.Add(new C{I = i, J = j});
}
cs.Count ().Dump("Size of input array");
TestGrouping(cs);
TestDistinct(cs);
}
public void TestGrouping(List<C> cs)
{
Stopwatch sw = Stopwatch.StartNew();
sw.Restart();
var groupedCount = cs.GroupBy (o => o.J).Select(s => s.First()).Count();
groupedCount.Dump("num groups");
sw.ElapsedMilliseconds.Dump("elapsed time for using grouping");
}
public void TestDistinct(List<C> cs)
{
Stopwatch sw = Stopwatch.StartNew();
var distinctCount = cs.Distinct(new CComparerOnJ()).Count ();
distinctCount.Dump("num distinct");
sw.ElapsedMilliseconds.Dump("elapsed time for using distinct");
}
public class C
{
public int I {get; set;}
public int J {get; set;}
}
public class CComparerOnJ : IEqualityComparer<C>
{
public bool Equals(C x, C y)
{
return x.J.Equals(y.J);
}
public int GetHashCode(C obj)
{
return obj.J.GetHashCode();
}
}
Try,
var newList =
(
from x in empCollection
select new {Loc = x.empLoc, PL = x.empPL, Shift = x.empShift}
).Distinct();

Categories