Combine two list elements into single element - c#

I have a list with two elements
element 1:
no:1,
vendor: a,
Description: Nice,
price :10
element 2:
no:1
vendor:a,
Description: Nice,
price:20
i have lot more fields in list elements so i cannot use new to sum the price
if everything is same except price i need to combine two elements into a single element by summing price.
o/p element 1:
no:1,
vendor:a,
Description:Nice,
price:30
Tried below one but not sure how to sum the price and return the entire fields with out using new
list.GroupBy(y => new { y.Description,y.vendor, y.no})
.Select(x => x.ToList().OrderBy(t => t.Price)).FirstOrDefault()

If you prefer LINQ query expressions:
var groupedElements = from element in elements
group element by new
{
element.no,
element.Description,
element.vendor
}
into grouped
select new {grouped, TotalPrice = grouped.Sum(x => x.price)};
The total price is calculated with the final .Sum method call on the grouped elements.

Try following :
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
List<Element> totals = elements.GroupBy(x => x.no).Select(x => new Element()
{
no = x.Key,
vendor = x.FirstOrDefault().vendor,
Description = x.FirstOrDefault().Description,
price = x.Sum(y => y.price)
}).ToList();
}
}
public class Element
{
public int no { get;set; }
public string vendor { get;set; }
public string Description { get;set; }
public decimal price { get;set; }
}
Try following using Clone
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
var groups = elements.GroupBy(x => x.no).ToList();
List<Element> totals = new List<Element>();
foreach (var group in groups)
{
Element newElement = (Element)group.FirstOrDefault().Clone();
newElement.price = group.Sum(x => x.price);
totals.Add(newElement);
}
}
}
public class Element : ICloneable
{
public int no { get;set; }
public string vendor { get;set; }
public string Description { get;set; }
public decimal price { get;set; }
public object Clone()
{
return this;
}
}
}

Willy-nilly you have to create Key which has 3 properties;
If you don't like the current solution with anonymous class
list
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
...
You can do it in different way, e.g. with a help of unnamed tuple:
list
.GroupBy(y => Tuple.Create(
y.Description,
y.vendor,
y.no)
)
...
Or named tuple (see https://learn.microsoft.com/en-us/dotnet/csharp/tuples for details):
list
.GroupBy(y => (
Description : y.Description,
vendor : y.vendor,
no : y.no)
)
...
Or even tailored class. What's matter the most, however, is that you can't just get First item from the group
but should create a new instance. Another issue is premature materialization: .ToList() when you then get rid of this new born list and keep on querying with .OrderBy(...)
var result = result
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
.Select(group => MyObject() { //TODO: put the right syntax here
Description = group.Key.Description,
vendor = group.Key.vendor,
no = group.Key.no,
price = group.Sum(item => item.price) // you want to sum prices, right?
});

You need to create a custom IEqualityComparer, which when passed into the GroupBy clause, will group the items according to your needs.
Asuming the following sample class:
public class Element
{
public int no { get; set; }
public string vendor { get; set; }
public string Description { get; set; }
public decimal price { get; set; }
}
You can implement the following IEqualityComparer which using Reflection will compare every Propertypresent in the Element class except the ones defined in the Linq Where clause, in this case "price". Bear in mind further customizations could be required.
public class ElementComparer : IEqualityComparer<Element>
{
public bool Equals(Element a, Element b) => typeof(Element).GetProperties()
.Where(p => p.Name != "price")
.All(p => p.GetValue(a).Equals(p.GetValue(b)));
public int GetHashCode(Element obj) => obj.no.GetHashCode();
}
Then simply group them this way
list.GroupBy(x => x, new ElementComparer()).Select(g =>
{
// Here you need to either clone the first element of the group like
// #jdweng did, or create a new instance of Element like I'm doing below
Element element = new Element();
foreach (var prop in element.GetType().GetProperties())
{
if (prop.Name == "price")
{
prop.SetValue(element, g.Sum(y => y.price));
}
else
{
prop.SetValue(element, prop.GetValue(g.First()));
}
}
return element;
});

I think what you're trying to do is write dynamic code that groups by all properties except for the property you want to sum. This solution should work, though I loath to use reflection. A more performant method would be to use expression trees to generate an aggregation delegate that you reuse, but that is very involved. This should do the trick:
Edit: There's another answer that also seems to work. Mine assumes you will want to do this with any collection regardless of type. Doesn't require ICloneable or a type-specific IEqualityComparer<T>, though, as a slight trade-off, the other one will likely perform better in very large datasets.
static T[] GetGroupSums<T>(IEnumerable<T> collection, string sumPropertyName) where T : new()
{
//get the PropertyInfo you want to sum
//var sumProp = (PropertyInfo)((MemberExpression)((UnaryExpression)memberExpression.Body).Operand).Member;
var sumProp = typeof(T).GetProperty(sumPropertyName);
//get all PropertyInfos that are not the property to sum
var groupProps = typeof(T).GetProperties().Where(x => x != sumProp).ToArray();
//group them by a hash of non-summed properties (I got this hash method off StackExchange many years back)
var groups = collection
.GroupBy(x => GetHash(groupProps.Select(pi => pi.GetValue(x)).ToArray()))
.Select(items =>
{
var item = new T();
var firstItem = items.First();
//clone the first item
foreach (var gp in groupProps)
{
gp.SetValue(item, gp.GetValue(firstItem));
}
//Get a decimal sum and then convert back to the sum property type
var sum = items.Sum(_item => (decimal)Convert.ChangeType(sumProp.GetValue(_item), typeof(decimal)));
sumProp.SetValue(item, Convert.ChangeType(sum, sumProp.PropertyType));
//If it will always be int, just do this
//var sum = items.Sum(_item => (int)sumProp.GetValue(_item));
//sumProp.SetValue(item, sum);
return item;
});
return groups.ToArray();
}
//I got this hash method off StackExchange many years back
public static int GetHash(params object[] args)
{
unchecked
{
int hash = 17;
foreach (object arg in args)
{
hash = hash * 23 + arg.GetHashCode();
}
return hash;
}
}
Use it like this:
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 2, vendor = "a", Description = "Nice", price = 15},
new Element() { no = 2, vendor = "b", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
var groups = GetGroupSums(elements, nameof(Element.price));

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 group objects that have the same lists of child objects?

I am trying to group a list of objects, the objects have a property which is a list. When using the following expression it does not group the objects together which have a list, presumably because when it checks the equality of each list, it doesn't match - even though the content is the same.
I know this is the issue, as if I stop grouping the list, the code outputs two items rather than three.
How can I adapt my expression so that it correctly groups the first two items (which are identical) into one?
public class Item {
public string Id { get; set; }
public string Name { get; set; }
public IEnumerable<ChildItem> Children { get; set; }
}
public class ChildItem {
public string ChildItemName { get; set; }
}
var items = new List<Item> {
new Item {
Id = "00001",
Name = "My Item",
Children = new List<ChildItem> {
new ChildItem { ChildItemName = "string 1" },
new ChildItem { ChildItemName = "string 2" },
}
},
new Item {
Id = "00001",
Name = "My Item",
Children = new List<ChildItem> {
new ChildItem { ChildItemName = "string 1" },
new ChildItem { ChildItemName = "string 2" },
}
},
new Item {
Id = "00002",
Name = "My Second Item",
Children = new List<ChildItem> {
new ChildItem { ChildItemName = "string 3" },
}
}
};
var result =
from c in items
group c by new
{
c.Id,
c.Name,
c.Children,
}
into gcs
select new Item()
{
Id = gcs.Key.Id,
Name = gcs.Key.Name,
Children = gcs.Key.Children
};
If items with the same Id have identical names and collections, you can just group by Id and then take the first item of the group
IEnumerable<Item> uniqueItems = items
.GroupBy(x => x.Id)
.Select(g => g.First());
You don't need to put everything into the group key.
Since, according to your comment, the Id does not guarantee uniqueness, you must provide your own definition of what "equal" means. You can do this by implementing your own equality comparers.
An implementation for ChildItem as singleton, since it will be used repeatedly in ItemEqualityComparer.Equals.
class ChildItemEqualityComparer : IEqualityComparer<ChildItem>
{
public static readonly ChildItemEqualityComparer Instance =
new ChildItemEqualityComparer(); // Create singleton.
private ChildItemEqualityComparer() { } // Hide constructor.
public bool Equals(ChildItem x, ChildItem y) =>
String.Equals(x.ChildItemName, y.ChildItemName);
public int GetHashCode(ChildItem childItem) =>
childItem.ChildItemName?.GetHashCode() ?? 0;
}
An implementation for Item:
class ItemEqualityComparer : IEqualityComparer<Item>
{
public bool Equals(Item x, Item y)
{
return x.Id == y.Id && x.Name == y.Name &&
Enumerable.SequenceEqual(x.Children, y.Children,
ChildItemEqualityComparer.Instance);
}
public int GetHashCode(Item item)
{
int hash = 43;
unchecked {
hash = 17 * hash + (item.Id?.GetHashCode() ?? 0);
hash = 17 * hash + (item.Name?.GetHashCode() ?? 0);
foreach (ChildItem childItem in item.Children) {
hash = 17 * hash + ChildItemEqualityComparer.Instance.GetHashCode(childItem);
}
}
return hash;
}
}
And the query:
var result = items.Distinct(new ItemEqualityComparer());
This yields 2 items.
You can write the GroupBy expression like this
items.GroupBy(x => x.Id);
If you just want to eliminate the duplicates you can just transform the collection to HashSet and back.
items.ToHashSet().ToList();
You will want to override the default implementation of GetHashCode though, to make sure that it is calculated using the fields of Item.
If you have to use ChildItem class (instead of a simple string) you
can consider implementing IEqualityComparer for ChildItem. This enables the use of
(System.Linq) SequenceEqual method.
ProductA[] storeA = { new ProductA { Name = "apple", Code = 9 },
new ProductA { Name = "orange", Code = 4 } };
ProductA[] storeB = { new ProductA { Name = "apple", Code = 9 },
new ProductA { Name = "orange", Code = 4 } };
bool equalAB = storeA.SequenceEqual(storeB);
Then you can think about implementing IEquatable interface in Item class.
The IEquatable interface defines the Equals method, which
determines the equality of instances of the implementing type
In Equals method you can use SequenceEqual to check equality Children which is described above.
3.Lastly you can use (System.Linq) Distinct() method to get unique objects for items.
IEnumerable<Item> noduplicates = items.Distinct();
Please check out:
https://learn.microsoft.com/en-gb/dotnet/api/system.collections.generic.iequalitycomparer-1?view=netframework-4.8
https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.sequenceequal?view=netframework-4.8
https://learn.microsoft.com/en-gb/dotnet/api/system.iequatable-1?view=netframework-4.8
https://learn.microsoft.com/en-gb/dotnet/api/system.linq.enumerable.distinct?view=netframework-4.8

Remove duplicate objects based on a list properties

I want to distinct a list of objects just based on some properties. These properties are gotten via reflection and some conditions. I searched a lot but cannot found any code snippets or solutions that are able to do a loop in this lambda expression.
List<PropertyInfo> propList = ...
var distinctList = FullList
.GroupBy(uniqueObj =>
{
//do a loop to iterate all elements in propList
})
.Select(x => x.First());
Ok, took me a while to think this one through.
Basically, you can use the Linq GroupBy operator, but you need to use the overload that accepts a custom IEQualityComparer, because you want to verify equality of the objects based on a subset of all their properties.
The subset of properties is stored in a List<PropertyInfo> that you created somewhere else in your code, or that you receive from a service or whatever.
So, implementing IEqualityComparer, then use it with GroupBy:
//Dummy class representing your data.
//
//Notice that I made the IEqualityComparer as a child class only
//for the sake of demonstration
public class DataObject
{
public string Name { get; set; }
public int Age { get; set; }
public int Grade { get; set; }
public static List<PropertyInfo> GetProps()
{
//Only return a subset of the DataObject class properties, simulating your List<PropertyInfo>
return typeof(DataObject).GetProperties().Where(p => p.Name == "Name" || p.Name == "Grade").ToList();
}
public class DataObjectComparer : IEqualityComparer<DataObject>
{
public bool Equals(DataObject x, DataObject y)
{
if (x == null || y == null)
return false;
foreach (PropertyInfo pi in DataObject.GetProps())
{
if (!pi.GetValue(x).Equals(pi.GetValue(y)))
return false;
}
return true;
}
public int GetHashCode(DataObject obj)
{
int hash = 17;
foreach (PropertyInfo pi in DataObject.GetProps())
{
hash = hash * 31 + pi.GetValue(obj).GetHashCode();
}
return hash;
}
}
}
//Then use that in your code:
//
List<DataObject> lst = new List<DataObject>();
lst.Add(new DataObject { Name = "Luc", Age = 49, Grade = 100 });
lst.Add(new DataObject { Name = "Luc", Age = 23, Grade = 100 });
lst.Add(new DataObject { Name = "Dan", Age = 49, Grade = 100 });
lst.Add(new DataObject { Name = "Dan", Age = 23, Grade = 100 });
lst.Add(new DataObject { Name = "Luc", Age = 20, Grade = 80 });
List<DataObject> dist = lst.GroupBy(p => p, new DataObject.DataObjectComparer()).Select(g => g.First()).ToList();
//The resulting list now contains distinct objects based on the `Name` and `Grade` properties only.
I hope this helps you get closer to your solution.
Cheers
You can create expression using the property name with this method:
public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(arg, propertyName);
//return the property as object
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
And use like this:
var exp = GetPropertySelector<Person>("PropertyName");
Now you can make a distinct easily:
List<Person> distinctPeople = allPeople
.GroupBy(exp.Compile())
.Select(g => g.First())
.ToList();

How to make select query by string property name in lambda expression?

I would like to make a query by using lambda select,
Like below:
public class Foo{
public int Id {get;set;}
public string Name {get;set;}
public string Surname {get;set;}
}
var list = new List<Foo>();
var temp = list.Select(x=> x("Name"),("Surname"));
The property name needs to be sent as a string,
I dont know how to use, I have given it for being a example.
is it possible?
Edit:
Foo list :
1 A B
2 C D
3 E F
4 G H
I don't know type of generic list, I have property name such as "Name", "Surname"
I want to be like below:
Result :
A B
C D
E F
G H
The following code snippet shows 2 cases. One filtering on the list, and another creating a new list of anonymous objects, having just Name and Surname.
List<Foo> list = new List<Foo>();
var newList = list.Select(x=> new {
AnyName1 = x.Name,
AnyName2 = x.Surname
});
var filteredList = list.Select(x => x.Name == "FilteredName" && x.Surname == "FilteredSurname");
var filteredListByLinq = from cust in list
where cust.Name == "Name" && cust.Surname == "Surname"
select cust;
var filteredByUsingReflection = list.Select(c => c.GetType().GetProperty("Name").GetValue(c, null));
Interface
If you have access to the types in question, and if you always want to access the same properties, the best option is to make the types implement the same interface:
public interface INamable
{
string Name { get; }
string Surname { get; }
}
public class Foo : INamable
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
This will preserve type safety and enable queries like this one:
public void ExtractUsingInterface<T>(IEnumerable<T> list) where T : INamable
{
var names = list.Select(o => new { Name = o.Name, Surname = o.Surname });
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
If, for some reason, you can't alter the original type, here are two more options.
Reflection
The first one is reflection. This is Mez's answer, i'll just rephrase it with an anonymous type like in the previous solution (not sure what you need exactly):
public void ExtractUsingReflection<T>(IEnumerable<T> list)
{
var names = list.Select(o => new
{
Name = GetStringValue(o, "Name"),
Surname = GetStringValue(o, "Surname")
});
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
private static string GetStringValue<T>(T obj, string propName)
{
return obj.GetType().GetProperty(propName).GetValue(obj, null) as string;
}
Dynamic
The second uses dynamic:
public void ExtractUsingDynamic(IEnumerable list)
{
var dynamicList = list.Cast<dynamic>();
var names = dynamicList.Select(d => new
{
Name = d.Name,
Surname = d.Surname
});
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
With that in place, the following code:
IEnumerable<INamable> list = new List<Foo>
{
new Foo() {Id = 1, Name = "FooName1", Surname = "FooSurname1"},
new Foo() {Id = 2, Name = "FooName2", Surname = "FooSurname2"}
};
ExtractUsingInterface(list);
// IEnumerable<object> list... will be fine for both solutions below
ExtractUsingReflection(list);
ExtractUsingDynamic(list);
will produce the expected output:
FooName1 FooSurname1
FooName2 FooSurname2
FooName1 FooSurname1
FooName2 FooSurname2
FooName1 FooSurname1
FooName2 FooSurname2
I'm sure you can fiddle with that and get to what you are trying to achieve.
var temp = list.Select(x => x.Name == "Name" && x.Surname == "Surname");
var temp = list.Select(x => new {Name = x.Name, Surname = x.Surname});

How can I use Lambda syntax to remove duplicates? [duplicate]

I have a class Items with properties (Id, Name, Code, Price).
The List of Items is populated with duplicated items.
For ex.:
1 Item1 IT00001 $100
2 Item2 IT00002 $200
3 Item3 IT00003 $150
1 Item1 IT00001 $100
3 Item3 IT00003 $150
How to remove the duplicates in the list using linq?
var distinctItems = items.GroupBy(x => x.Id).Select(y => y.First());
var distinctItems = items.Distinct();
To match on only some of the properties, create a custom equality comparer, e.g.:
class DistinctItemComparer : IEqualityComparer<Item> {
public bool Equals(Item x, Item y) {
return x.Id == y.Id &&
x.Name == y.Name &&
x.Code == y.Code &&
x.Price == y.Price;
}
public int GetHashCode(Item obj) {
return obj.Id.GetHashCode() ^
obj.Name.GetHashCode() ^
obj.Code.GetHashCode() ^
obj.Price.GetHashCode();
}
}
Then use it like this:
var distinctItems = items.Distinct(new DistinctItemComparer());
If there is something that is throwing off your Distinct query, you might want to look at MoreLinq and use the DistinctBy operator and select distinct objects by id.
var distinct = items.DistinctBy( i => i.Id );
This is how I was able to group by with Linq. Hope it helps.
var query = collection.GroupBy(x => x.title).Select(y => y.FirstOrDefault());
An universal extension method:
public static class EnumerableExtensions
{
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> keySelector)
{
return enumerable.GroupBy(keySelector).Select(grp => grp.First());
}
}
Example of usage:
var lstDst = lst.DistinctBy(item => item.Key);
You have three option here for removing duplicate item in your List:
Use a a custom equality comparer and then use Distinct(new DistinctItemComparer()) as #Christian Hayter mentioned.
Use GroupBy, but please note in GroupBy you should Group by all of the columns because if you just group by Id it doesn't remove duplicate items always. For example consider the following example:
List<Item> a = new List<Item>
{
new Item {Id = 1, Name = "Item1", Code = "IT00001", Price = 100},
new Item {Id = 2, Name = "Item2", Code = "IT00002", Price = 200},
new Item {Id = 3, Name = "Item3", Code = "IT00003", Price = 150},
new Item {Id = 1, Name = "Item1", Code = "IT00001", Price = 100},
new Item {Id = 3, Name = "Item3", Code = "IT00003", Price = 150},
new Item {Id = 3, Name = "Item3", Code = "IT00004", Price = 250}
};
var distinctItems = a.GroupBy(x => x.Id).Select(y => y.First());
The result for this grouping will be:
{Id = 1, Name = "Item1", Code = "IT00001", Price = 100}
{Id = 2, Name = "Item2", Code = "IT00002", Price = 200}
{Id = 3, Name = "Item3", Code = "IT00003", Price = 150}
Which is incorrect because it considers {Id = 3, Name = "Item3", Code = "IT00004", Price = 250} as duplicate. So the correct query would be:
var distinctItems = a.GroupBy(c => new { c.Id , c.Name , c.Code , c.Price})
.Select(c => c.First()).ToList();
3.Override Equal and GetHashCode in item class:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public int Price { get; set; }
public override bool Equals(object obj)
{
if (!(obj is Item))
return false;
Item p = (Item)obj;
return (p.Id == Id && p.Name == Name && p.Code == Code && p.Price == Price);
}
public override int GetHashCode()
{
return String.Format("{0}|{1}|{2}|{3}", Id, Name, Code, Price).GetHashCode();
}
}
Then you can use it like this:
var distinctItems = a.Distinct();
Use Distinct() but keep in mind that it uses the default equality comparer to compare values, so if you want anything beyond that you need to implement your own comparer.
Please see http://msdn.microsoft.com/en-us/library/bb348436.aspx for an example.
Try this extension method out. Hopefully this could help.
public static class DistinctHelper
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
var identifiedKeys = new HashSet<TKey>();
return source.Where(element => identifiedKeys.Add(keySelector(element)));
}
}
Usage:
var outputList = sourceList.DistinctBy(x => x.TargetProperty);
List<Employee> employees = new List<Employee>()
{
new Employee{Id =1,Name="AAAAA"}
, new Employee{Id =2,Name="BBBBB"}
, new Employee{Id =3,Name="AAAAA"}
, new Employee{Id =4,Name="CCCCC"}
, new Employee{Id =5,Name="AAAAA"}
};
List<Employee> duplicateEmployees = employees.Except(employees.GroupBy(i => i.Name)
.Select(ss => ss.FirstOrDefault()))
.ToList();
Another workaround, not beautiful buy workable.
I have an XML file with an element called "MEMDES" with two attribute as "GRADE" and "SPD" to record the RAM module information.
There are lot of dupelicate items in SPD.
So here is the code I use to remove the dupelicated items:
IEnumerable<XElement> MList =
from RAMList in PREF.Descendants("MEMDES")
where (string)RAMList.Attribute("GRADE") == "DDR4"
select RAMList;
List<string> sellist = new List<string>();
foreach (var MEMList in MList)
{
sellist.Add((string)MEMList.Attribute("SPD").Value);
}
foreach (string slist in sellist.Distinct())
{
comboBox1.Items.Add(slist);
}
When you don't want to write IEqualityComparer you can try something like following.
class Program
{
private static void Main(string[] args)
{
var items = new List<Item>();
items.Add(new Item {Id = 1, Name = "Item1"});
items.Add(new Item {Id = 2, Name = "Item2"});
items.Add(new Item {Id = 3, Name = "Item3"});
//Duplicate item
items.Add(new Item {Id = 4, Name = "Item4"});
//Duplicate item
items.Add(new Item {Id = 2, Name = "Item2"});
items.Add(new Item {Id = 3, Name = "Item3"});
var res = items.Select(i => new {i.Id, i.Name})
.Distinct().Select(x => new Item {Id = x.Id, Name = x.Name}).ToList();
// now res contains distinct records
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}

Categories