I have a List of objects
public class sample
{
public DateTime Date;
public string content;
}
I want to be able to create a list of new o
Here's a non-Linq way to do it:
List<sampleWithIntervals> groups = new List<sampleWithIntervals>();
sampleWithIntervals curGroup = null;
foreach(sample s in samples.OrderBy(sa => sa.content).ThenBy(sa => sa.Date))
{
if(curGroup == null || // first group
s.Date != curGroup.endDate.AddDays(1) ||
s.content != curGroup.content // new group
)
{
curGroup = new sampleWithIntervals() {startDate = s.Date, endDate = s.Date, content = s.content};
groups.Add(curGroup);
}
else
{
// add to current group
curGroup.endDate = s.Date;
}
}
You can do this with Linq using a trick that groups the items by the date minus the index to group consecutive items:
samples.OrderBy(s => s.content)
.ThenBy(s => s.Date)
// select each item with its index
.Select ((s, i) => new {sample = s, index = i})
// group by date miuns index to group consecutive items
.GroupBy(si => new {date = si.sample.Date.AddDays(-si.index), content = si.sample.content})
// get the min, max, and content of each group
.Select(g => new sampleWithIntervals() {
startDate = g.Min(s => s.sample.Date),
endDate = g.Max(s => s.sample.Date),
content = g.First().sample.content
})
I have this SplitBy
extension method where you can specify a delimiter predicate with which the collection will be split, just like string.Split
.
public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source,
Func<T, bool> delimiterPredicate,
bool includeEmptyEntries = false,
bool includeSeparator = false)
{
var l = new List<T>();
foreach (var x in source)
{
if (!delimiterPredicate(x))
l.Add(x);
else
{
if (includeEmptyEntries || l.Count != 0)
{
if (includeSeparator)
l.Add(x);
yield return l;
}
l = new List<T>();
}
}
if (l.Count != 0 || includeEmptyEntries)
yield return l;
}
So now splitting is easy if you can specify a consecutive streak delimiter. For that you can order the collection and zip with adjacent items, so now the difference in dates in the two resultant columns can act as delimiter.
var ordered = samples.OrderBy(x => x.content).ThenBy(x => x.Date).ToArray();
var result = ordered.Zip(ordered.Skip(1).Append(new sample()), (start, end) => new { start, end })
.SplitBy(x => x.end.Date - x.start.Date != TimeSpan.FromDays(1), true, true)
.Select(x => x.Select(p => p.start).ToArray())
.Where(x => x.Any())
.Select(x => new sampleWithIntervals
{
content = x.First().content,
startDate = x.First().Date,
endDate = x.Last().Date
});
new sample()
is a dummy instance used to get the Zip
correctly. The Append
method is to append items to a IEnumerable<>
sequence, and it goes like this:
public static IEnumerable<T> Append<T>(this IEnumerable<T> source, params T[] items)
{
return source.Concat(items);
}
Note: this doesn't preserve the initial order. If you want the original order, select the index initially and form an anonymous class right away (Select((x, i) => new { x, i })
) and at the last stage sort on the basis of index before selecting appropriate type.