linq 如何使用基于两个参数的.GroupBy?

lo8azlld  于 2023-03-10  发布在  其他
关注(0)|答案(2)|浏览(222)

我有一个数据,我需要根据两个字段进行拆分。我正在使用.GroupBy来做这件事,但看起来我没有得到所需的结果。
下面是我拆分数据的条件:
1.用supplierName分割它们。(这可以简单地用.GroupBy来完成。第二点对我来说有点棘手)
1.如果第1个记录集包含相同的partTypeCode,则将其拆分为单独的记录。
例如,以下是我拥有的数据集,其中3条记录来自autozone,1条记录来自orielly。我希望在Desired Outcome中提到数据。

我的方法:

1.这是给我的第一个点的结果,但分组数据的OIL_FILTER,这不是我想要的。

var groupedList = items.GroupBy(x => x.supplierName)
                   .Select(g => new {
                       Key = g.Key,
                       Count = g.Count(),
                       itemGroup = g.GroupBy(x => x.partTypeCode)
                   }).ToList();

1.这也俱乐部N记录与partTypeCode(这看起来很明显,从我的第二个过滤器)

var groupedList = items.GroupBy(x => new { x.supplierName, x.partTypeCode }).ToList();

问题:

  • .GroupBy是实现我想要的东西的正确方法吗?
  • 如果没有,什么是达到我想要的结果的理想方法?
    数据:
[
    {
        "cartItemId": "7de1881e-41b3-493f-a342-c8e7c7d6c6ff",
        "partTypeCode": "OIL_FILTER",
        "quantity": 1,
        "manufacturerCode": "CHL",
        "supplierName": "autozone"
    },
    {
        "cartItemId": "111881e-41b3-493f-a342-c8e7c7d6c6ff",
        "partTypeCode": "VEHICLE_BATTERY",
        "quantity": 1,
        "manufacturerCode": "CHL",
        "supplierName": "autozone"
    },
    {
        "cartItemId": "222881e-41b3-493f-a342-c8e7c7d6c6ff",
        "partTypeCode": "F_DS_WIPER_BLADE",
        "quantity": 1,
        "manufacturerCode": "CHL",
        "supplierName": "orielly"
    },
    {
        "cartItemId": "3331881e-41b3-493f-a342-c8e7c7d6c6ff",
        "partTypeCode": "OIL_FILTER",
        "quantity": 1,
        "manufacturerCode": "CHL",
        "supplierName": "autozone"
    }
]

预期成果:

1st collection
[
    {
        "cartItemId": "7de1881e-41b3-493f-a342-c8e7c7d6c6ff",
        "partTypeCode": "OIL_FILTER",
        "quantity": 1,
        "manufacturerCode": "CHL",
        "supplierName": "autozone"
    },
    {
        "cartItemId": "111881e-41b3-493f-a342-c8e7c7d6c6ff",
        "partTypeCode": "VEHICLE_BATTERY",
        "quantity": 1,
        "manufacturerCode": "CHL",
        "supplierName": "autozone"
    }
]

2nd collection
[
    {
        "cartItemId": "222881e-41b3-493f-a342-c8e7c7d6c6ff",
        "partTypeCode": "F_DS_WIPER_BLADE",
        "quantity": 1,
        "manufacturerCode": "CHL",
        "supplierName": "orielly"
    }
]

3rd collection
[
    {
        "cartItemId": "3331881e-41b3-493f-a342-c8e7c7d6c6ff",
        "partTypeCode": "OIL_FILTER",
        "quantity": 1,
        "manufacturerCode": "CHL",
        "supplierName": "autozone"
    }
]
8yparm6h

8yparm6h1#

听起来您需要首先按supplierName和partTypeCode分组,在每个组内分配序号,然后部分地展平结果,再按supplierName和序号重新分组。
下面的方法可能有效:

var groupedList = items
    .GroupBy(item => new { item.supplierName, item.partTypeCode })
    .SelectMany(grp => grp.Select((item, index) => new { item, index } ))
    .GroupBy(
        ix => new { ix.item.supplierName, ix.index }, // New grouping
        ix => ix.item // item selector
    )
    .ToList();

工作原理:
1.首先,您的集合按supplierNamepartTypeCode分组。如果这些组中的任何一个包含多个项目,我们将希望在最终结果中将它们拆分为单独的组。

  1. (暂时忽略.SelectMany()。)但是在它内部,我们取每个组中的项,并创建一个新的组合对象,其中包含该组中的项和索引,我们使用.Select()的重载形式,它向lambda函数提供项和序列号。每个组中的项目(也可能是唯一的)与index = 0配对。任何其他项目将与index1, 2, 3,配对,依此类推。
    1.包含.SelectMany()的现在将把这些集合扁平化为项/索引对的单个流。
    1.接下来,我们应用另一个.GroupBy(),但这次我们将按supplierNameindex分组。对于每个supplierName,如果只有具有不同partTypeCode值的项,则所有索引值都将为0,并且所有索引值都将被收集到一个组中。每个不同的索引值(每个supplierName)将产生一个单独的组。.GroupBy()重载的第二个参数是项选择器,我们使用它来向下钻取项/索引对以提取原始项。
    1.最后,.ToList()将所有这些合并到最终结果中。
    如果您关心哪些项归入哪个组,可以在步骤2中在.Select()之前添加.OrderBy()
    您还可以对.ToList()之前的最后一组应用排序。可能是.OrderBy(grp => grp.Key.supplierName).ThenBy(grp => grp.Key.index),或者相反。
    如果需要列表的列表而不是分组的列表,可以在最后的.ToList()之前插入.Select(grp => grp.ToList()).Select(grp => grp.OrderBy(...).ToList())
    完整的声明与所有的铃铛和哨子将是:
var groupedList = items
    .GroupBy(item => new { item.supplierName, item.partTypeCode })
    .SelectMany(grp => grp
        .OrderByDescending(item => item.quantity)
        .Select((item, index) => new { item, index })
    )
    .GroupBy(
        ix => new { ix.item.supplierName, ix.index }, // New grouping
        ix => ix.item // item selector
    )
    .OrderBy(grp => grp.Key.supplierName)
    .ThenBy(grp => grp.Key.index)
    .Select(grp => grp.OrderBy(item => item.partTypeCode).ToList())
    .ToList();
nwlls2ji

nwlls2ji2#

这似乎是一个最好通过自定义LINQ扩展方法来解决的问题,但这里有一个使用普通LINQ的答案:

var ans = items.GroupBy(item => item.supplierName) // group by supplierName
               .Select(item_sg => item_sg.GroupBy(item => item.partTypeCode) // for each supplierName, group by partTypeCode
                                         .Select(item_pg => new { // return new object
                                                     items = item_pg.ToList(), // preserve original items
                                                     Count = item_pg.Count() // count of duplicate partTypeCodes
                                                 }))
               .SelectMany(icg => Enumerable.Range(1, icg.Max(ic => ic.Count)) // 1 .. max # of dups
                                            .Select(gnum => icg.Where(ic => ic.Count >= gnum) // filter to partTypeCodes for gnum'th group
                                                               .Select(ic => ic.items[gnum-1]) // back to original data
                                                               .ToList()));

相反,我建议使用一种自定义扩展方法--这种方法使用嵌套的Dictionary对象按键拆分组,并将重复的购物车项目移动到下一个组,因此,它不保留原始顺序。

public static class IEnumerableExt {
    public static IEnumerable<IEnumerable<T>> GroupWithNoDups<T, TGroupKey, TDupKey>(this IEnumerable<T> src, Func<T, TGroupKey> groupKeyFn, Func<T, TDupKey> dupKeyFn) {
        var ansGroupsMap = new Dictionary<TGroupKey, List<Dictionary<TDupKey, T>>>();
        foreach (var cartItem in src) {
            if (!ansGroupsMap.TryGetValue(groupKeyFn(cartItem), out var ansGroups)) {
                ansGroups = new List<Dictionary<TDupKey, T>>();
                ansGroupsMap[groupKeyFn(cartItem)] = ansGroups;
            }
            var addedToGroup = false;
            foreach (var ansGroup in ansGroups) {
                if (!ansGroup.ContainsKey(dupKeyFn(cartItem))) {
                    ansGroup[dupKeyFn(cartItem)] = cartItem;
                    addedToGroup = true;
                    break;
                }
            }
            if (!addedToGroup) {
                var newAnsGroup = new Dictionary<TDupKey, T>();
                newAnsGroup[dupKeyFn(cartItem)] = cartItem;
                ansGroups.Add(newAnsGroup);
            }
        }
        foreach (var ansGroups in ansGroupsMap.Values)
            foreach (var ansGroup in ansGroups)
                yield return ansGroup.Values;
    }
}

您可以像这样使用它:

var ans = items.GroupWithNoDups(i => i.supplierName, i => i.partyTypeCode);

相关问题