满足所有要求的更好解决方案
好的,放弃我以前的解决方案(我将其留在下面,仅供参考)。这是我在发布初始帖子后想到的一种更好的方法。
编写一个实现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,它可以工作。
原始、凌乱且有点愚蠢的解决方案
有些事情告诉我,我会因为这样说而遭到完全驳斥,但是......
是的,这是可能的(我认为)。请参阅下面的该死我拼凑的混乱解决方案。 (捕获异常以了解何时完成,因此您知道这是一个很棒的设计!)
现在,Jon 的观点是,如果您尝试执行 ToList,然后按索引访问结果列表中的值,则会出现一个非常实际的问题,这是完全有效的。但是,如果您的 only 意图是能够使用 foreach 循环 IEnumerable<T> - 而您 only 在您的 自己的代码——那么,我认为这对你有用。
不管怎样,这里有一个简单的例子来说明它是如何工作的:
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
现在是(乱七八糟的)代码:
请注意,由于这种方法需要在几个不同的方法之间传递实际的 枚举器,因此如果该枚举器是值类型,它将不起作用,就像对 @ 的调用987654344@ 在一种方法中只影响本地副本。
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;
}
}