C# 在列表中合并两个或多个T<;T>;基于条件

C# 在列表中合并两个或多个T<;T>;基于条件,c#,linq,collections,C#,Linq,Collections,我有以下课程: public class FactoryOrder { public string Text { get; set; } public int OrderNo { get; set; } } 和保存FactoryOrder列表的集合 List<FactoryOrder>() 我的要求是合并FactoryOrder的文本,其中orderNo按顺序排列,并保留合并FactoryOrder的较低orderN

我有以下课程:

public class FactoryOrder
    {
        public string Text { get; set; }
        public int OrderNo { get; set; }        
    }
和保存FactoryOrder列表的集合

List<FactoryOrder>()
我的要求是合并FactoryOrder的文本,其中orderNo按顺序排列,并保留合并FactoryOrder的较低orderNo -因此,结果输出将是

   FactoryOrder("Apple Orange",20) //Merged Apple and Orange and retained Lower OrderNo 20
    FactoryOrder("WaterMelon",42)
    FactoryOrder("JackFruit",51)
    FactoryOrder("Grapes mango Cherry",71)//Merged Grapes,Mango,cherry and retained Lower OrderNo 71

我是Linq的新手,所以不知道该怎么做。任何帮助或指示都将不胜感激

我不确定使用一个可理解的LINQ表达式是否可以做到这一点。使用简单的枚举即可:

    private static IEnumerable<FactoryOrder> Merge(IEnumerable<FactoryOrder> orders)
    {
        var enumerator = orders.OrderBy(x => x.OrderNo).GetEnumerator();

        FactoryOrder previousOrder = null;
        FactoryOrder mergedOrder = null;

        while (enumerator.MoveNext())
        {
            var current = enumerator.Current;

            if (mergedOrder == null)
            {
                mergedOrder = new FactoryOrder(current.Text, current.OrderNo);
            }
            else
            {
                if (current.OrderNo == previousOrder.OrderNo + 1)
                {
                    mergedOrder.Text += current.Text;
                }
                else
                {
                    yield return mergedOrder;
                    mergedOrder = new FactoryOrder(current.Text, current.OrderNo);
                }
            }

            previousOrder = current;
        }

        if (mergedOrder != null)
            yield return mergedOrder;
    }
私有静态IEnumerable合并(IEnumerable订单)
{
var枚举器=orders.OrderBy(x=>x.OrderNo).GetEnumerator();
FactoryOrder previousOrder=null;
FactoryOrder mergedOrder=null;
while(枚举数.MoveNext())
{
var current=枚举数。当前值;
if(mergedOrder==null)
{
mergedOrder=新工厂订单(current.Text,current.OrderNo);
}
其他的
{
if(current.OrderNo==previousOrder.OrderNo+1)
{
mergedOrder.Text+=当前的.Text;
}
其他的
{
收益合并器;
mergedOrder=新工厂订单(current.Text,current.OrderNo);
}
}
前序=当前;
}
if(mergedOrder!=null)
收益合并器;
}

这假设
FactoryOrder
有一个接受文本和订单号的构造函数。

如注释所示,如果您的逻辑严重依赖于连续项,那么LINQ不是最简单的方法。使用一个简单的循环

您可以先使用LINQ:
orders.OrderBy(x=>x.OrderNo)

您的样品:


刚刚编写了一个方法,它结构紧凑,性能相当好:

    static List<FactoryOrder> MergeValues(List<FactoryOrder> dirtyList)
    {            
        FactoryOrder[] temp1 = dirtyList.ToArray();
        int index = -1;
        for (int i = 1; i < temp1.Length; i++)
        {
            if (temp1[i].OrderNo - temp1[i - 1].OrderNo != 1) { index = -1; continue; }
            if(index == -1 ) index = dirtyList.IndexOf(temp1[i - 1]); 
            dirtyList[index].Text += " " + temp1[i].Text;                
            dirtyList.Remove(temp1[i]);
        }
        return dirtyList;
    }
静态列表合并值(列表目录列表)
{            
FactoryOrder[]temp1=dirtyList.ToArray();
int指数=-1;
for(int i=1;i
使用副作用的Linq实施:

var groupId = 0;
var previous = Int32.MinValue;
var grouped = GetItems()
    .OrderBy(x => x.OrderNo)
    .Select(x =>
    {
        var @group = x.OrderNo != previous + 1 ? (groupId = x.OrderNo) : groupId;
        previous = x.OrderNo;
        return new
                {
                    GroupId = group,
                    Item = x
                };
    })
    .GroupBy(x => x.GroupId)
    .Select(x => new FactoryOrder(
       String.Join(" ", x.Select(y => y.Item.Text).ToArray()), 
       x.Key))
    .ToArray();

foreach (var item in grouped)
{
    Console.WriteLine(item.Text + "\t" + item.OrderNo);
}
输出:

Apple Orange    20
WaterMelon  42
JackFruit   51
Grapes mango Cherry 71
或者,使用生成器扩展方法消除副作用

public static class IEnumerableExtensions
{
    public static IEnumerable<IList<T>> MakeSets<T>(this IEnumerable<T> items, Func<T, T, bool> areInSameGroup)
    {
        var result = new List<T>();
        foreach (var item in items)
        {
            if (!result.Any() || areInSameGroup(result[result.Count - 1], item))
            {
                result.Add(item);
                continue;
            }
            yield return result;
            result = new List<T> { item };
        }
        if (result.Any())
        {
            yield return result;
        }
    }
}

输出相同,但代码更易于遵循和维护。

LINQ+顺序处理=

虽然没有说使用
Aggregate
始终是最好的选择。
for(each)
循环中的顺序处理通常有助于更好地读取代码(参见Tim的答案)。无论如何,这里有一个纯LINQ解决方案

它循环遍历订单,并首先将它们收集到字典中,其中连续订单的第一个
Id
Key
,订单的集合为
Value
。然后它使用
字符串生成一个结果。Join

类别:

class FactoryOrder
{
    public FactoryOrder(int id, string name)
    {
        this.Id = id;
        this.Name = name;
    }

    public int Id { get; set; }
    public string Name { get; set; }
}
该方案:

IEnumerable<FactoryOrder> orders =
    new[]
    {
        new FactoryOrder(20, "Apple"),
        new FactoryOrder(21, "Orange"),
        new FactoryOrder(22, "Pear"),
        new FactoryOrder(42, "WaterMelon"),
        new FactoryOrder(51, "JackFruit"),
        new FactoryOrder(71, "Grapes"),
        new FactoryOrder(72, "Mango"),
        new FactoryOrder(73, "Cherry"),
    };


var result = orders.OrderBy(t => t.Id).Aggregate(new Dictionary<int, List<FactoryOrder>>(),
    (dir, curr) =>
    {
        var prevId = dir.SelectMany(d => d.Value.Select(v => v.Id))
            .OrderBy(i => i).DefaultIfEmpty(-1)
            .LastOrDefault();
        var newKey = dir.Select(d => d.Key).OrderBy(i => i).LastOrDefault();
        if (prevId == -1 || curr.Id - prevId > 1)
        {
            newKey = curr.Id;
        }
        if (!dir.ContainsKey(newKey))
        {
            dir[newKey] = new List<FactoryOrder>();
        }
        dir[newKey].Add(curr);

        return dir;
    }, c => c)
    .Select(t => new
                 {
                     t.Key, 
                     Items = string.Join(" ", t.Value.Select(v => v.Name))
                 }).ToList();
IEnumerable订单=
新[]
{
新工厂订单(20,“苹果”),
新工厂订单(21,“橙色”),
新工厂订单(22,“Pear”),
新工厂订单(42,“西瓜”),
新工厂订单(51,“菠萝蜜”),
新工厂订单(71,“葡萄”),
新工厂订单(72,“芒果”),
新工厂订单(73,“樱桃”),
};
var result=orders.OrderBy(t=>t.Id).Aggregate(新字典(),
(直接,当前)=>
{
var prevId=dir.SelectMany(d=>d.Value.Select(v=>v.Id))
.OrderBy(i=>i).DefaultIfEmpty(-1)
.LastOrDefault();
var newKey=dir.Select(d=>d.Key).OrderBy(i=>i.LastOrDefault();
如果(prevId==-1 | | curr.Id-prevId>1)
{
newKey=curr.Id;
}
如果(!dir.ContainsKey(newKey))
{
dir[newKey]=新列表();
}
目录[newKey]。添加(当前);
返回目录;
},c=>c)
.选择(t=>new
{
t、 钥匙,
Items=string.Join(“,t.Value.Select(v=>v.Name))
}).ToList();
正如您所看到的,这里发生的事情并不简单,而且当有“许多”项时,它的性能可能会很差,因为不断增长的字典会被一次又一次地访问


这是一个冗长的说法:不要使用聚合。

如果您的逻辑严重依赖于连续项,那么LINQ不是最简单的方法。使用一个简单的循环。你可以先用LINQ订购:
orders.Orderby(x=>x.OrderNo)
@TimSchmelter我甚至不认为有一种“理智的”“纯粹的”LINQ方法可以做到这一点…@xanatos:这取决于你所说的纯粹。有LINQ扩展方法,因此可以通过相邻项对其进行分组。但是他们只是隐藏了很多复杂的代码。类似。@xanatos:但即使是该实现也不关心属性的连续值,而是关心序列中属性中具有相同值的连续项。所以你是对的,我不知道任何实现。@TimSchmelter显然构建一个完整的程序并将其隐藏在LINQ后面不是“纯粹的”:-,但它仍然是一个很好的解决方案。
public static class IEnumerableExtensions
{
    public static IEnumerable<IList<T>> MakeSets<T>(this IEnumerable<T> items, Func<T, T, bool> areInSameGroup)
    {
        var result = new List<T>();
        foreach (var item in items)
        {
            if (!result.Any() || areInSameGroup(result[result.Count - 1], item))
            {
                result.Add(item);
                continue;
            }
            yield return result;
            result = new List<T> { item };
        }
        if (result.Any())
        {
            yield return result;
        }
    }
}
var grouped = GetItems()
    .OrderBy(x => x.OrderNo)
    .MakeSets((prev, next) => next.OrderNo == prev.OrderNo + 1)
    .Select(x => new FactoryOrder(
        String.Join(" ", x.Select(y => y.Text).ToArray()),
        x.First().OrderNo))
    .ToList();

foreach (var item in grouped)
{
    Console.WriteLine(item.Text + "\t" + item.OrderNo);
}
class FactoryOrder
{
    public FactoryOrder(int id, string name)
    {
        this.Id = id;
        this.Name = name;
    }

    public int Id { get; set; }
    public string Name { get; set; }
}
IEnumerable<FactoryOrder> orders =
    new[]
    {
        new FactoryOrder(20, "Apple"),
        new FactoryOrder(21, "Orange"),
        new FactoryOrder(22, "Pear"),
        new FactoryOrder(42, "WaterMelon"),
        new FactoryOrder(51, "JackFruit"),
        new FactoryOrder(71, "Grapes"),
        new FactoryOrder(72, "Mango"),
        new FactoryOrder(73, "Cherry"),
    };


var result = orders.OrderBy(t => t.Id).Aggregate(new Dictionary<int, List<FactoryOrder>>(),
    (dir, curr) =>
    {
        var prevId = dir.SelectMany(d => d.Value.Select(v => v.Id))
            .OrderBy(i => i).DefaultIfEmpty(-1)
            .LastOrDefault();
        var newKey = dir.Select(d => d.Key).OrderBy(i => i).LastOrDefault();
        if (prevId == -1 || curr.Id - prevId > 1)
        {
            newKey = curr.Id;
        }
        if (!dir.ContainsKey(newKey))
        {
            dir[newKey] = new List<FactoryOrder>();
        }
        dir[newKey].Add(curr);

        return dir;
    }, c => c)
    .Select(t => new
                 {
                     t.Key, 
                     Items = string.Join(" ", t.Value.Select(v => v.Name))
                 }).ToList();