将两个列表元素合并为单个元素



我有一个包含两个元素的列表

要素1:

no:1,
vendor: a,
Description: Nice,
price :10

要素2:

no:1
vendor:a,
Description: Nice,
price:20 

我在列表元素中有更多的字段,所以我不能使用 new 来求和价格

如果除价格外一切都相同,我需要通过对价格求和将两个元素组合成一个元素。

O/P 元素 1:

no:1,
vendor:a,
Description:Nice,
price:30

尝试过下面的一个,但不确定如何对价格求和并返回整个字段,而无需使用 new

list.GroupBy(y => new { y.Description,y.vendor, y.no})
.Select(x => x.ToList().OrderBy(t => t.Price)).FirstOrDefault()

如果您更喜欢 LINQ 查询表达式:

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)};

总价是通过对分组元素的最终.Sum方法调用来计算的。

尝试以下操作:

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; }
}

尝试使用克隆关注

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;
}
}
}

无论如何,您必须创建具有3属性的Key;

如果您不喜欢当前带有匿名类的解决方案

list
.GroupBy(y => new { 
y.Description,
y.vendor, 
y.no}
)
...

你可以用不同的方式做到这一点,例如,在未命名元组的帮助下:

list
.GroupBy(y => Tuple.Create(
y.Description, 
y.vendor, 
y.no)
)
...

命名元组(有关详细信息,请参阅 https://learn.microsoft.com/en-us/dotnet/csharp/tuples(:

list
.GroupBy(y => (
Description : y.Description,
vendor      : y.vendor, 
no          : y.no)
)
...

甚至是量身定制的课程。然而,最重要的是,你不能只从groupFirst物品 但应该创建一个新实例。另一个问题是过早实现.ToList()当你摆脱这个新生列表并继续查询.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?
});

您需要创建一个自定义IEqualityComparer,当传递到GroupBy子句中时,它将根据您的需要对项目进行分组。

假设以下示例类:

public class Element
{
public int no { get; set; }
public string vendor { get; set; }
public string Description { get; set; }
public decimal price { get; set; }
}

您可以实现以下IEqualityComparer,使用Reflection将比较Element类中存在的每个Property,但 LinqWhere子句中定义的除外,在本例中为"price"。请记住,可能需要进一步的自定义。

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();
}

然后简单地以这种方式对它们进行分组

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;
});

我认为您要做的是编写动态代码,按除要求和的属性之外的所有属性进行分组。这个解决方案应该有效,尽管我不愿意使用反射。性能更高的方法是使用表达式树生成可重用的聚合委托,但这非常复杂。这应该可以解决问题:

编辑:还有另一个答案似乎也有效。我的假设您希望对任何集合执行此操作,无论类型如何。不需要ICloneable或特定于类型的IEqualityComparer<T>,但是,作为轻微的权衡,另一个可能会在非常大的数据集中表现得更好。

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;
}
}

像这样使用它:

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));

最新更新