Группа A Список объектов [] (надеюсь, с Linq) - PullRequest
1 голос
/ 31 июля 2010

Скажем, у меня есть коллекция массивов объектов одинакового размера, например:

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. Но мой Линь-фу подводит меня. Есть идеи?

Ответы [ 3 ]

2 голосов
/ 31 июля 2010

@ Решение Мэтью Уайтеда было бы хорошо, если вы знаете, как группировать колонки заранее.Тем не менее, похоже, что вам нужно определить их во время выполнения.В этом случае вы можете создать компаратор равенства, который определяет равенство строк для 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.

1 голос
/ 31 июля 2010

Если ваша коллекция содержит элементы с индексатором (например, 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);
}
0 голосов
/ 31 июля 2010

Кратчайшее решение:

    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])));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...