Группировка по последовательности в LINQ


Предположим, что ряд объектов (в качестве кортежей):

"а" | 1
"а" | 2
"Б" | 3
"Б" | 4
"а" | 5

Нет встроенная функция (что я знаю) в группе первых столбцах по последовательности, то есть, все "А"подряд, затем "б", тогда "а" в покое. Так что группы становятся: {1,2},{3,4},{5} и не {1,2,5},{3,4}.

Поэтому я написала такое, что я представить для рассмотрения. Я подражать все 8 вариантов метода groupBy , который я представляю здесь в качестве двух основных вариантах (с и без результата селектор):

public static IEnumerable<IGrouping<TKey, TElement>> GroupBySequence<TSource, TKey, TElement>
    (this TSource[] source,
     Func<TSource, TKey> keySelector,
     Func<TSource, TElement> elementSelector,
     IEqualityComparer<TKey> comparer)
{
    var newElement = source.Select(keySelector).ToArray().MakeSequentialKey(comparer).Zip(
        source.Select(elementSelector),
        (x, y) => new Tuple<int, TElement>(x, y));

    var groupElement = newElement.GroupBy(t => t.Item1, t => t.Item2);

    var newKey = source.Select(keySelector).ToArray().MakeSequentialKey(comparer).Zip(
        source.Select(keySelector),
        (x, y) => new Tuple<int, TKey>(x, y));

    var groupKey = newKey.GroupBy(t => t.Item1, t => t.Item2);

    return groupKey.Zip(groupElement, 
        (key,element) => new Grouping<TKey,TElement>(key.First(),element));
}

public static IEnumerable<TResult> GroupBySequence<TSource, TKey, TElement, TResult>
    (this TSource[] source,
     Func<TSource, TKey> keySelector,
     Func<TSource, TElement> elementSelector,
     Func<TKey, IEnumerable<TElement>, TResult> resultSelector,
     IEqualityComparer<TKey> comparer)
{
    return source.GroupBySequence(keySelector, 
        elementSelector, comparer).Select(x => resultSelector(x.Key, x));
}

Вспомогательные методы:

//Performs an operation over each consecutive item. Here used for determining equality.
public static IEnumerable<TResult> WithNext<T, TResult>
    (this T[] source, Func<T, T, TResult> operation)
{
    return source.Zip(source.Skip(1), operation);
}

//Makes the unique key
public static IEnumerable<int> MakeSequentialKey<T>
    (this T[] source, IEqualityComparer<T> comparer)
{
    if (source.Length == 0)
        return Enumerable.Empty<int>();

    return (new[] { 0 })
        .Concat(source.ToArray().WithNext<T, int>((x, y) => comparer.Equals(x, y) ? 0 : 1))
        .ToArray()
        .RunningSum();
}

//Sum of all previous elements up to each item of an array
public static IEnumerable<int> RunningSum(this int[] source)
{
    int cumul = 0;
    foreach (int i in source)
        yield return cumul += i;
}

И классовая группировка, которая является в значительной степени прямым реализации IGrouping:

public class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    TKey key;
    IEnumerable<TElement> elements;

    public Grouping(TKey key, IEnumerable<TElement> elements)
    {
        this.key = key;
        this.elements = elements;
    }

    public TKey Key { get { return key; } }

    public IEnumerator<TElement> GetEnumerator()
    {
        return elements.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return elements.GetEnumerator();
    }
}

Предполагаемые вопросы:

  • Каков ваш общий подход здесь? Создания действительно уникального ключа от данного ключа, группу элементов и ключ при том, что и как реформировать новых групп с группировкой или применить результат к нему, так что оригинальный ключ типа до сих пор используется.
  • Почему степени т[] а не интерфейс IEnumerable? Потому что использование, как это означает, что элементы упорядочены. Ему не было бы смысла использовать GroupBySequence за словарь или поиска HashSet, которые реализуют интерфейс IEnumerable, потому что эти две коллекции, насколько мне известно, нет понятия порядка. Если есть лучше или чище способ указать на это, я не знаю.

Я ищу критику, предложения по ясности и лучшей практики. Спасибо за ваше время.



6536
7
задан 3 декабря 2011 в 06:12 Источник Поделиться
Комментарии
3 ответа

Как в мой комментарий:


Если {1,2},{3,4},{5} это ваш желаемый результат, я не понимаю все
сложности, которые вы добавили. Вы не можете просто написать простой цикл
предметов, который дает результат каждый раз проходя группе?

public static IEnumerable<IGrouping<TKey, TElement>> GroupBySequence<TSource, TKey, TElement>(
this TSource[] source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> comparer)
{
if (source.Length == 0)
{
yield break;
}

TKey currentKey = keySelector(source.First());
var foundItems = new List<TElement>();
foreach (var item in source)
{
TKey key = keySelector(item);

if (!comparer.Equals(currentKey, key))
{
yield return new Grouping<TKey, TElement>(currentKey, foundItems);
currentKey = key;
foundItems = new List<TElement>();
}

foundItems.Add(elementSelector(item));
}

if (foundItems.Count > 0)
{
yield return new Grouping<TKey, TElement>(currentKey, foundItems);
}
}

5
ответ дан 8 декабря 2011 в 02:12 Источник Поделиться

Вот еще один подход (меньше по LINQ, немного более код):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace testGrouping
{
static class GroupBySequenceExtension
{
internal class Grouping<TKey, TVal> : IGrouping<TKey, TVal>
{
public TKey Key { get; set; }
public IEnumerable<TVal> Items { get; set; }
public IEnumerator<TVal> GetEnumerator()
{
return Items.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return Items.GetEnumerator();
}
}

public static IEnumerable<IGrouping<TKey, TElement>> GroupBySequence<TSource, TKey, TElement>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> keyComparer)
{
TKey lastKey = default(TKey);
bool atFirst = true;
List<TElement> items = new List<TElement>();

foreach (var item in source)
{
var key = keySelector(item);
var element = elementSelector(item);
if (atFirst)
{
lastKey = key;
atFirst = false;
}

if (keyComparer.Equals(key, lastKey))
{
items.Add(element);
}
else
{
yield return new Grouping<TKey, TElement>
{
Key = lastKey,
Items = items
};
items = new List<TElement>();
items.Add(element);
}

lastKey = key;
}

if (items.Count > 0)
{
yield return new Grouping<TKey, TElement>
{
Key = lastKey,
Items = items
};
}
}
}
}

1
ответ дан 8 декабря 2011 в 06:12 Источник Поделиться

Для этого, я бы не использовать LINQ здесь. Там, к сожалению, нет никаких методов, чтобы облегчить эту задачу. На самом деле, я бы сказал, пытается использовать то, что в настоящее время доступно делает его более сложным и неэффективным, чем оно должно. Как вы можете видеть по всем вспомогательные методы и еще много чего нужно добавлять, можно увидеть, насколько сложным это может быть.

Просто заставить его работать на интерфейс IEnumerableнет никакого смысла в ограничении его массивов. Уверен, hashsets том, и словари не имеют понятие заказ, но как еще вы могли бы сделать это доступным для других "заказал" enumerables? Вам придется добавить перегрузок для каждого типа вы хотите поддержать, так нет реализованные интерфейсы, что и это различие. Было бы легче, если бы он работал для всех enumerables и уходить, когда использовать его, чтобы пользователю вашего кода.

Вообще у меня есть некоторый код, который делает что-то подобное. Вы только действительно нужно отслеживать предыдущий ключ, который был добавлен. Если текущий ключ совпадает с предыдущей, засунуть его в ту же группу, в противном случае создать новый.

public static partial class EnumerableEx
{
public static IEnumerable<IGrouping<TKey, TSource>> GroupByConsecutive<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
return GroupByConsecutive(source, keySelector, null as IEqualityComparer<TKey>);
}

public static IEnumerable<IGrouping<TKey, TSource>> GroupByConsecutive<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
{
return GroupByConsecutive(source, keySelector, Functions.Identity<TSource>, keyComparer);
}

public static IEnumerable<IGrouping<TKey, TElement>> GroupByConsecutive<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector)
{
return GroupByConsecutive(source, keySelector, elementSelector, null as IEqualityComparer<TKey>);
}

public static IEnumerable<IGrouping<TKey, TElement>> GroupByConsecutive<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> keyComparer)
{
return ConsecutiveGrouper<TKey, TElement>.Create(source, keySelector, elementSelector, keyComparer as IEqualityComparer<TKey>);
}

internal class ConsecutiveGrouper<TKey, TElement> : IEnumerable<IGrouping<TKey, TElement>>
{
internal static ConsecutiveGrouper<TKey, TElement> Create<TSource, TKey, TElement>(
IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> keyComparer)
{
source.ThrowIfNull("source");
keySelector.ThrowIfNull("keySelector");
elementSelector.ThrowIfNull("elementSelector");

var grouper = new ConsecutiveGrouper<TKey, TElement>(keyComparer);
foreach (var item in source)
{
grouper.NextGroup(keySelector(item)).Add(elementSelector(item));
}
return grouper;
}

private ConsecutiveGrouper(IEqualityComparer<TKey> keyComparer)
{
this._keyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
this._groupings = new List<Grouping>();
this._lastGrouping = null;
}
private IEqualityComparer<TKey> _keyComparer;
private List<Grouping> _groupings;
private Grouping _lastGrouping;

private Grouping NextGroup(TKey key)
{
if (_lastGrouping == null)
{
_lastGrouping = new Grouping(key);
_groupings.Add(_lastGrouping);
}
else if (!_keyComparer.Equals(_lastGrouping.Key, key))
{
_lastGrouping = new Grouping(key);
_groupings.Add(_lastGrouping);
}
return _lastGrouping;
}

public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator()
{
return _groupings.GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

class Grouping : IGrouping<TKey, TElement>
{
internal Grouping(TKey key)
{
this.Key = key;
this._elements = new List<TElement>();
}
public TKey Key { get; private set; }
private List<TElement> _elements;

internal void Add(TElement element)
{
_elements.Add(element);
}

public IEnumerator<TElement> GetEnumerator()
{
return _elements.GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}

С. С., В Python модуле itertools.метод groupBy() итератор имеет ту же семантику, что вы хотите, насколько я могу сказать. Возможно, вы захотите взглянуть на эквивалентные реализации.

0
ответ дан 4 декабря 2011 в 02:12 Источник Поделиться