var schedules = new List- {
new Item { Id=1, Name = \"S\" },
new Item { Id=2, Name = \"P\" },
new Item { Id=3, Name = \"X\" },
new Item { Id=4,
Based on the comment clarifications (the question is really unclear now), I think this is what is needed.
It uses an extension method that groups runs of keys together, GroupByRuns
, that is based on a GroupByWhile
the groups by testing consecutive items, which is based on ScanPair
, which is a variation of my APL inspired Scan
operator that is like Aggregate
, but returns intermediate results, and uses a ValueTuple
(Key, Value)
to pair keys with values along the way.
public static IEnumerable> GroupByRuns(this IEnumerable src, Func keySelector, Func resultSelector, IEqualityComparer cmp = null) {
cmp = cmp ?? EqualityComparer.Default;
return src.GroupByWhile((prev, cur) => cmp.Equals(keySelector(prev), keySelector(cur)), resultSelector);
}
public static IEnumerable> GroupByRuns(this IEnumerable src, Func keySelector) => src.GroupByRuns(keySelector, e => e);
public static IEnumerable> GroupByRuns(this IEnumerable src) => src.GroupByRuns(e => e, e => e);
public static IEnumerable> GroupByWhile(this IEnumerable src, Func testFn, Func resultFn) =>
src.ScanPair(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1)
.GroupBy(kvp => kvp.Key, kvp => resultFn(kvp.Value));
public static IEnumerable<(TKey Key, T Value)> ScanPair(this IEnumerable src, TKey seedKey, Func<(TKey Key, T Value),T,TKey> combineFn) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var prevkv = (seedKey, srce.Current);
while (srce.MoveNext()) {
yield return prevkv;
prevkv = (combineFn(prevkv, srce.Current), srce.Current);
}
yield return prevkv;
}
}
}
I realize this is a lot of extension code, but by using the general ScanPair
base, you can build other specialized grouping methods, such as GroupBySequential
.
Now you just GroupByRuns
of Name
and select the runs with more than one member, then convert each run to a List
and the whole thing to a List
:
var ans = schedules.GroupByRuns(s => s.Name)
.Where(sg => sg.Count() > 1)
.Select(sg => sg.ToList())
.ToList();
NOTE: For @Aominè, who had an interesting take on optimizing Count() > 1
using Take(2).Count()
or @MichaelGunter using Skip(1).Any()
, after GroupBy
the sub-groups (internal type Grouping
) each implement IList
and the Count()
method just gets the count directly from the Grouping.count
field.