Скажем, у меня есть набор массивов объектов одинаковой размерности, например:
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 ответа
Решение @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. Вам потребуется расширить List<object[]>
непосредственно в .NET 3.5.
Если ваша коллекция содержит элементы с индексатором (например, ваш 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);
}
Кратчайшее решение:
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])));
Похожие вопросы
Новые вопросы
c#
C# (произносится как «see Sharp») — это высокоуровневый мультипарадигменный язык программирования со статической типизацией, разработанный Microsoft. Код C# обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, которое включает в себя .NET, .NET Framework, .NET MAUI и Xamarin среди прочих. Используйте этот тег для ответов на вопросы о коде, написанном на C#, или о формальной спецификации C#.
xor
хэш-коды. Если вы это сделаете, вы увеличите вероятность столкновения.GetHashCode
. Однако я хотел избежать этого беспорядочного обсуждения, поэтому выбрал подход с минимальным трением.GetHashCode
. Я включил это, потому чтоColumnComparer
является общедоступным типом. Если вы сделаете этоprivate
, где вы можете абсолютно гарантировать отсутствие нулей, то его можно безопасно удалить. Однако в будущем, пожалуйста, воздержитесь от стилистических изменений, таких как добавление локальной переменной вCombineColumnHashCodes
. Для меня это лишнее, и я не хочу, чтобы его приняли за написанный мной код. Спасибо.