代码之家  ›  专栏  ›  技术社区  ›  Tim Scott

将对象列表[]分组(希望使用LINQ)

  •  1
  • Tim Scott  · 技术社区  · 15 年前

    假设我有一个等维的对象数组集合,如下所示:

    var rows = new List<object[]>
    {
        new object[] {1, "test1", "foo", 1},
        new object[] {1, "test1", "foo", 2},
        new object[] {2, "test1", "foo", 3},
        new object[] {2, "test2", "foo", 4},
    };
    

    我想按一个或多个“列”分组——哪些列在运行时动态确定。例如,按列1、2和3分组将产生三个组:

    • 组1:[1,“测试1”,“foo”](包括第1行和第2行)
    • 组2:[2,“测试1”,“foo”](包括第3行)
    • 组3:[2,“测试2”,“foo”](包括第4行)

    当然,我可以通过某种自定义组类以及排序和迭代来实现这一点。不过,似乎我应该能够用LINQ分组更清晰地进行操作。但我的林福让我失望了。有什么想法吗?

    3 回复  |  直到 15 年前
        1
  •  2
  •   Tim Scott    15 年前

    @如果您知道前面的分组列,那么MatthewWhite的解决方案是很好的。但是,听起来您需要在运行时确定它们。在这种情况下,可以创建一个相等比较器,该比较器定义 GroupBy 使用可配置的列集:

    rows.GroupBy(row => row, new ColumnComparer(0, 1, 2))
    

    比较器检查每个指定列的值是否相等。它还组合每个值的哈希代码:

    public class ColumnComparer : IEqualityComparer<object[]>
    {
        private readonly IList<int> _comparedIndexes;
    
        public ColumnComparer(params int[] comparedIndexes)
        {
            _comparedIndexes = comparedIndexes.ToList();
        }
    
        #region IEqualityComparer
    
        public bool Equals(object[] x, object[] y)
        {
            return ReferenceEquals(x, y) || (x != null && y != null && ColumnsEqual(x, y));
        }
    
        public int GetHashCode(object[] obj)
        {
            return obj == null ? 0 : CombineColumnHashCodes(obj);
        }    
        #endregion
    
        private bool ColumnsEqual(object[] x, object[] y)
        {
            return _comparedIndexes.All(index => ColumnEqual(x, y, index));
        }
    
        private bool ColumnEqual(object[] x, object[] y, int index)
        {
            return Equals(x[index], y[index]);
        }
    
        private int CombineColumnHashCodes(object[] row)
        {
            return _comparedIndexes
                .Select(index => row[index])
                .Aggregate(0, (hashCode, value) => hashCode ^ (value == null ? 0 : value.GetHashCode()));
        }
    }
    

    如果这是您经常要做的事情,您可以将其置于扩展方法之后:

    public static IGrouping<object[], object[]> GroupByIndexes(
        this IEnumerable<object[]> source,
        params int[] indexes)
    {
        return source.GroupBy(row => row, new ColumnComparer(indexes));
    }
    
    // Usage
    
    row.GroupByIndexes(0, 1, 2)
    

    延伸 IEnumerable<object[]> 只适用于.NET 4。你需要延长 List<object[]> 直接在.NET 3.5中。

        2
  •  1
  •   Matthew Whited    15 年前

    如果集合包含带有索引器的项(例如 object[] 你可以这样做…

    var byColumn = 3;
    
    var rows = new List<object[]> 
    { 
        new object[] {1, "test1", "foo", 1}, 
        new object[] {1, "test1", "foo", 2}, 
        new object[] {2, "test1", "foo", 3}, 
        new object[] {2, "test2", "foo", 4}, 
    };
    
    var grouped = rows.GroupBy(k => k[byColumn]);
    var otherGrouped = rows.GroupBy(k => new { k1 = k[1], k2 = k[2] });
    

    …如果您不喜欢上面的静态集,也可以直接在LINQ中做一些更有趣的事情。这将假设您的散列码适用于相等的计算。 注意,您可能只想写一个 IEqualityComparer<T>

    var cols = new[] { 1, 2};
    
    var grouped = rows.GroupBy(
        row => cols.Select(col => row[col])
                   .Aggregate(
                        97654321, 
                        (a, v) => (v.GetHashCode() * 12356789) ^ a));
    
    foreach (var keyed in grouped)
    {
        Console.WriteLine(keyed.Key);
        foreach (var value in keyed)
            Console.WriteLine("{0}|{1}|{2}|{3}", value);
    }
    
        3
  •  0
  •   Grozz    15 年前

    最短解决方案:

        int[] columns = { 0, 1 };
    
        var seed = new[] { rows.AsEnumerable() }.AsEnumerable();    // IEnumerable<object[]> = group, IEnumerable<group> = result
    
        var result = columns.Aggregate(seed, 
            (groups, nCol) => groups.SelectMany(g => g.GroupBy(row => row[nCol])));