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

时间:2019-10-14 11:16:04

标签: c# linq

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

元素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

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

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

5 个答案:

答案 0 :(得分:0)

尝试以下操作:

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

}

答案 1 :(得分:0)

您需要创建一个自定义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可以比较Property类中存在的每个Element,但Linq Where子句中定义的除外,在这种情况下为"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;
});

答案 2 :(得分:0)

非常讨厌,您必须创建具有Key个属性的3

如果您不喜欢使用匿名类

的当前解决方案
list
  .GroupBy(y => new { 
     y.Description,
     y.vendor, 
     y.no}
   )
  ...

您可以通过其他方式进行操作,例如借助未命名元组

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

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

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

甚至是定制类。但是,最重要的是,您不能只是从First获取 group项目 但应创建一个新实例。另一个问题是过早实现.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?
     });

答案 3 :(得分:0)

如果您喜欢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方法调用来计算的。

答案 4 :(得分:0)

我认为您要尝试编写的动态代码将要汇总的属性按所有属性(除 以外)分组。尽管我不愿意使用反射,但是该解决方案应该可以工作。一种性能更高的方法是使用表达式树来生成您重复使用的聚合委托,但这非常涉及。这应该可以解决问题:

编辑:还有另一个答案似乎也可行。我的假设您将要对任何集合执行此操作,而无论其类型如何。不需要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));