更好的解决方案,满足所有需求
好的,废弃我以前的解决方案(我将把它留在下面,仅供参考)。这里有一个更好的方法,在我做了最初的职位后发生在我身上。
编写一个实现
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>
实现的类
i分组<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
这是有效的。
原始的,混乱的,有点愚蠢的解决方案
有件事告诉我说这个我会被完全驳倒,但是…
是的
,这是可能的(我想)。见下文
该死
我把杂乱的解决方案放在一起。(捕获一个异常以知道它何时结束,所以您
知道
这是一个伟大的设计!)
现在,乔恩的观点是,在你试图做的事情中存在一个非常真实的问题,例如,
托利斯特
,然后按索引访问结果列表中的值,是完全有效的。但是如果你
只有
这里的目的是能够循环
IEnumerable<t>
使用A
前额
你是
只有
在你的
拥有
代码——那么,好吧,我想这对你来说是可行的。
不管怎样,这里有一个简单的例子说明它是如何工作的:
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
现在,对于(乱七八糟的)代码:
注意,由于这种方法需要传递
枚举器
在几个不同的方法之间,
不会工作
如果该枚举器是值类型,则调用
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;
}
}