Linq Get Collection of collocated equal objects
The following code shows what I want to do:
public static IEnumerable<IEnumerable<T>> DoIt<T>(this IEnumerable<T> that)
{
if (that == null)
throw new ArgumentNullException();
if (that.Count() > 1)
{
var result = new Collection<IEnumerable<T>>();
var collection = new Collection<T>();
collection.Add(that.ElementAt(0));
for (int i = 1; i < that.Count(); ++i)
{
if (!that.ElementAt(i).Equals(that.ElementAt(i - 1)))
{
result.Add(collection);
collection = new Collection<T>();
}
collection.Add(that.ElementAt(i));
}
result.Add(collection);
return result;
}
return new Collection<IEnumerable<T>>() { that };
}
I'm only using custom implementations like that one, if ther开发者_StackOverflow中文版e is no appropriate implementation already existing. Is there any way to do the same with the standard framework?
There is no traditional way to do this with the standard framework. I do have a couple of issues with your solution though.
- The use of
ElementAt(i)
is very inefficient and can cause thethat
collection to be iterated many, many times. This can lead to performance issues - The use of
Count
also can be costly as it can cause a full enumeration ofthat
- Unlike most LINQ methods it doesn't use deferred execution. To fix this you will need to use a
yield return
style solution.
Here's an alternative solution
public static IEnumerable<IEnumerable<T>> DoIt<T>(this IEnumerable<T> that) {
using (var e = that.GetEnumerator()) {
if (!e.MoveNext()) {
yield break;
}
bool hasMore;
do {
var item = e.Current;
var list = new List<T>();
list.Add(item);
hasMore = e.MoveNext();
while (hasMore && item.Equals(e.Current)) {
list.Add(e.Current);
hasMore = e.MoveNext();
}
yield return list;
} while (hasMore);
}
}
You can use SequenceEqual which "Determines whether two sequences are equal by comparing the elements by using the default equality comparer for their type" in case you are dealing with ordered collections http://msdn.microsoft.com/en-us/library/bb348567.aspx
Otherwise collection1.Intersect(collection2).Count()==collection1.Count
will do the trick
It is possible to do in a chained statement. I am not sure if I would advise code like this though!
public static IEnumerable<IEnumerable<T>> DoIt<T>(this IEnumerable<T> that) {
return that.Zip(that.Skip(1), (a, b) => a.Equals(b) ? 1 : 0)
.Aggregate(
(IEnumerable<int>)new []{1, 0},
(c, x) => (new []{c.First() + 1}).Concat(c.Skip(x)),
_ => _.Zip(_.Skip(1), (to, skip) => new {skip, take = to - skip}))
.Reverse()
.Select(_ => that.Skip(_.skip).Take(_.take));
}
Use the Any() extension method
精彩评论