Way лучшее решение, которое отвечает всем требованиям
Хорошо, отложите мое предыдущее решение (я оставлю его ниже, только для справки). Вот намного лучший подход, который произошел со мной после моего первоначального сообщения.
Создайте новый класс, который реализует IEnumerator<T>
и предоставляет дополнительные свойства: IsValid
и Previous
. Это все, что вам действительно нужно, чтобы решить весь беспорядок с необходимостью поддерживать состояние внутри блока итератора, используя yield
.
Вот как я это сделал (довольно тривиально, как вы можете видеть):
internal class ChipmunkEnumerator<T> : IEnumerator<T> {
private readonly IEnumerator<T> _internal;
private T _previous;
private bool _isValid;
public ChipmunkEnumerator(IEnumerator<T> e) {
_internal = e;
_isValid = false;
}
public bool IsValid {
get { return _isValid; }
}
public T Previous {
get { return _previous; }
}
public T Current {
get { return _internal.Current; }
}
public bool MoveNext() {
if (_isValid)
_previous = _internal.Current;
return (_isValid = _internal.MoveNext());
}
public void Dispose() {
_internal.Dispose();
}
#region Explicit Interface Members
object System.Collections.IEnumerator.Current {
get { return Current; }
}
void System.Collections.IEnumerator.Reset() {
_internal.Reset();
_previous = default(T);
_isValid = false;
}
#endregion
}
(я назвал это ChipmunkEnumerator
, потому что сохранение предыдущего значения напомнил мне о том, как бурундуки есть мешочки на щеках, где они держат орехи. Это действительно имеет значение? Перестань смеяться надо мной.)
Теперь, используя этот класс в методе расширения, чтобы обеспечить именно то поведение, которое вы хотите, не так сложно!
Обратите внимание, что ниже я определил GroupConsecutive
на самом деле вернуть IEnumerable<IGrouping<TKey, T>>
по той простой причине, что, если они сгруппированы по ключевым в любом случае, имеет смысл вернуть IGrouping<TKey, T>
, а не просто IEnumerable<T>
. Как оказалось, это поможет нам позже все равно ...
public static IEnumerable<IGrouping<TKey, T>> GroupConsecutive<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
where TKey : IEquatable<TKey> {
using (var e = new ChipmunkEnumerator<T>(source.GetEnumerator())) {
if (!e.MoveNext())
yield break;
while (e.IsValid) {
yield return e.GetNextDuplicateGroup(keySelector);
}
}
}
public static IEnumerable<IGrouping<T, T>> GroupConsecutive<T>(this IEnumerable<T> source)
where T : IEquatable<T> {
return source.GroupConsecutive(x => x);
}
private static IGrouping<TKey, T> GetNextDuplicateGroup<T, TKey>(this ChipmunkEnumerator<T> e, Func<T, TKey> keySelector)
where TKey : IEquatable<TKey> {
return new Grouping<TKey, T>(keySelector(e.Current), e.EnumerateNextDuplicateGroup(keySelector));
}
private static IEnumerable<T> EnumerateNextDuplicateGroup<T, TKey>(this ChipmunkEnumerator<T> e, Func<T, TKey> keySelector)
where TKey : IEquatable<TKey> {
do {
yield return e.Current;
} while (e.MoveNext() && keySelector(e.Previous).Equals(keySelector(e.Current)));
}
(Для реализации этих методов, я написал простой Grouping<TKey, T>
класс, который реализует IGrouping<TKey, T>
самым простым способом. Я опустил код просто чтобы продолжить движение ...)
Хорошо, проверьте. Я думаю, что пример кода ниже довольно хорошо отражает что-то похожее на более реалистичный сценарий, который вы описали в своем обновленном вопросе.
var entries = new List<KeyValuePair<string, int>> {
new KeyValuePair<string, int>("Dan", 10),
new KeyValuePair<string, int>("Bill", 12),
new KeyValuePair<string, int>("Dan", 14),
new KeyValuePair<string, int>("Dan", 20),
new KeyValuePair<string, int>("John", 1),
new KeyValuePair<string, int>("John", 2),
new KeyValuePair<string, int>("Bill", 5)
};
var dupeGroups = entries
.GroupConsecutive(entry => entry.Key);
foreach (var dupeGroup in dupeGroups) {
Console.WriteLine(
"Key: {0} Sum: {1}",
dupeGroup.Key.PadRight(5),
dupeGroup.Select(entry => entry.Value).Sum()
);
}
Выход:
Key: Dan Sum: 10
Key: Bill Sum: 12
Key: Dan Sum: 34
Key: John Sum: 3
Key: Bill Sum: 5
Обратите внимание, это также устраняет проблему с моим оригинальным ответом дела с IEnumerator<T>
объектов, которые были типами значений. (При таком подходе это не имеет значения.)
Здесь все еще будет проблема, если вы попробуете позвонить ToList
здесь, как вы узнаете, попробуйте ли вы это. Но учитывая, что вы включили отложенное исполнение в качестве требования , я сомневаюсь, что вы все равно это сделаете. Для foreach
он работает.
Оригинал, Грязное, и в некоторой степени Stupid решение
Что-то подсказывает мне, что я собираюсь получить полностью опровергнуты за эти слова, но ...
Да, возможно (Я думаю). См. Ниже для damn messy solution Я бросил вместе. (Ловит исключение знать, когда она будет закончена, так что вы знаете это отличный дизайн!)
Теперь точку Джона о там быть очень реальная проблема в том случае, если вы пытаетесь сделать, например, ToList
, и затем получить доступ к значениям в результирующем списке по индексу, является полностью допустимым. Но если ваш только намерение здесь, чтобы быть в состоянии петли над IEnumerable<T>
с помощью foreach
- и вы только делает это в собственного кода - то, ну, я думаю, что это может работать для вас ,
Во всяком случае, вот краткий пример того, как это работает:
var ints = new int[] { 1, 3, 3, 4, 4, 4, 5, 2, 3, 1, 6, 6, 6, 5, 7, 7, 8 };
var dupeGroups = ints.GroupConsecutiveDuplicates(EqualityComparer<int>.Default);
foreach (var dupeGroup in dupeGroups) {
Console.WriteLine(
"New dupe group: " +
string.Join(", ", dupeGroup.Select(i => i.ToString()).ToArray())
);
}
Выход:
New dupe group: 1
New dupe group: 3, 3
New dupe group: 4, 4, 4
New dupe group: 5
New dupe group: 2
New dupe group: 3
New dupe group: 1
New dupe group: 6, 6, 6
New dupe group: 5
New dupe group: 7, 7
New dupe group: 8
А теперь для (грязный, как дерьмо) Код:
Примечание что, поскольку этот подход требует прохождения фактического перечислителя вокруг betw een несколько разных методов, это не будет работать, если этот перечислитель является типом значения, поскольку вызовы MoveNext
одним способом затрагивают только локальную копию.
public static IEnumerable<IEnumerable<T>> GroupConsecutiveDuplicates<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer) {
using (var e = source.GetEnumerator()) {
if (e.GetType().IsValueType)
throw new ArgumentException(
"This method will not work on a value type enumerator."
);
// get the ball rolling
if (!e.MoveNext()) {
yield break;
}
IEnumerable<T> nextDuplicateGroup;
while (e.FindMoreDuplicates(comparer, out nextDuplicateGroup)) {
yield return nextDuplicateGroup;
}
}
}
private static bool FindMoreDuplicates<T>(this IEnumerator<T> enumerator, IEqualityComparer<T> comparer, out IEnumerable<T> duplicates) {
duplicates = enumerator.GetMoreDuplicates(comparer);
return duplicates != null;
}
private static IEnumerable<T> GetMoreDuplicates<T>(this IEnumerator<T> enumerator, IEqualityComparer<T> comparer) {
try {
if (enumerator.Current != null)
return enumerator.GetMoreDuplicatesInner(comparer);
else
return null;
} catch (InvalidOperationException) {
return null;
}
}
private static IEnumerable<T> GetMoreDuplicatesInner<T>(this IEnumerator<T> enumerator, IEqualityComparer<T> comparer) {
while (enumerator.Current != null) {
var current = enumerator.Current;
yield return current;
if (!enumerator.MoveNext())
break;
if (!comparer.Equals(current, enumerator.Current))
break;
}
}
IM предполагая в вашем образце ответа вы имеете в виду { «б», «б», «б»} –
@Josh: Хороший улов - я исправил вопрос, спасибо! –
В вашем сложном примере Sum должен повторить сборку во второй раз. Какой смысл ограничивать «Группировку» на одну итерацию, если вызывающий код снова будет повторять те же элементы? –