对象列表[](希望与Linq一起)

时间:2010-07-31 04:25:54

标签: c# linq c#-3.0

假设我有一组相同维度的对象数组,如下所示:

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,“test1”,“foo”](包括第1行和第2行)
  • 第2组:[2,“test1”,“foo”](包括第3行)
  • 第3组:[2,“test2”,“foo”](包括第4行)

当然,我可以通过某种自定义组类以及排序和迭代来实现这一点。但是,看起来我应该能够用Linq分组来做得更干净。但我的Linq-fu让我失望了。有什么想法吗?

3 个答案:

答案 0 :(得分:2)

@Matthew Whited的解决方案很好,如果你事先知道分组列。但是,听起来你需要在运行时确定它们。在这种情况下,您可以创建一个相等比较器,它使用可配置的列集定义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.您需要在.NET 3.5中直接扩展List<object[]>

答案 1 :(得分:1)

如果您的收藏中包含带有索引器的项目(例如您的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中做一些更有趣的事情。这将假设您的HashCodes将适用于Equals评估。 注意,您可能只想撰写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);
}

答案 2 :(得分:0)

最短的解决方案:

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