Say I have the following array of integers:
int[] numbers = { 1, 6, 4, 10, 9, 12, 15, 17, 8, 3, 20, 21, 2, 23, 25, 27, 5, 67,33, 13, 8, 12, 41, 5 };
<
UPDATE: While not technically a "linq query" as Patrick points out in the comments, this solution is reusable, flexible, and generic.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication32
{
class Program
{
static void Main(string[] args)
{
int[] numbers = { 1, 6, 4, 10, 9, 12, 15, 17, 8, 3, 20, 21, 2, 23, 25, 27, 5, 67,33, 13, 8, 12, 41, 5 };
var consecutiveGroups = numbers.FindConsecutiveGroups((x) => x > 10, 3);
foreach (var group in consecutiveGroups)
{
Console.WriteLine(String.Join(",", group));
}
}
}
public static class Extensions
{
public static IEnumerable<IEnumerable<T>> FindConsecutiveGroups<T>(this IEnumerable<T> sequence, Predicate<T> predicate, int count)
{
IEnumerable<T> current = sequence;
while (current.Count() > count)
{
IEnumerable<T> window = current.Take(count);
if (window.Where(x => predicate(x)).Count() >= count)
yield return window;
current = current.Skip(1);
}
}
}
}
Output:
12,15,17
23,25,27
67,33,13
To get the 2nd group, change:
var consecutiveGroups = numbers.FindConsecutiveGroups((x) => x > 10, 3);
To:
var consecutiveGroups = numbers.FindConsecutiveGroups((x) => x > 10, 3).Skip(1).Take(1);
UPDATE 2 After tweaking this in our production use, the following implementation is far faster as the count of items in the numbers array grows larger.
public static IEnumerable<IEnumerable<T>> FindConsecutiveGroups<T>(this IEnumerable<T> sequence, Predicate<T> predicate, int sequenceSize)
{
IEnumerable<T> window = Enumerable.Empty<T>();
int count = 0;
foreach (var item in sequence)
{
if (predicate(item))
{
window = window.Concat(Enumerable.Repeat(item, 1));
count++;
if (count == sequenceSize)
{
yield return window;
window = window.Skip(1);
count--;
}
}
else
{
count = 0;
window = Enumerable.Empty<T>();
}
}
}
int[] numbers = { 1, 6, 4, 10, 9, 12, 15, 17, 8, 3, 20, 21, 2, 23, 25, 27, 5, 67, 33, 13, 8, 12, 41, 5 };
var numbersQuery = numbers.Select((x, index) => new { Index = index, Value = x});
var query = from n in numbersQuery
from n2 in numbersQuery.Where(x => n.Index == x.Index - 1).DefaultIfEmpty()
from n3 in numbersQuery.Where(x => n.Index == x.Index - 2).DefaultIfEmpty()
where n.Value > 10
where n2 != null && n2.Value > 10
where n3 != null && n3.Value > 10
select new
{
Value1 = n.Value,
Value2 = n2.Value,
Value3 = n3.Value
};
In order to specify which group, you can call the Skip
method
query.Skip(1)
Why don't you try this extension method?
public static IEnumerable<IEnumerable<T>> Consecutives<T>(this IEnumerable<T> numbers, int ranges, Func<T, bool> predicate)
{
IEnumerable<T> ordered = numbers.OrderBy(a => a).Where(predicate);
decimal n = Decimal.Divide(ordered.Count(), ranges);
decimal max = Math.Ceiling(n); // or Math.Floor(n) if you want
return from i in Enumerable.Range(0, (int)max)
select ordered.Skip(i * ranges).Take(ranges);
}
The only thing to improve could be the call to Count
method because causes the enumeration of numbers
(so the query loses its laziness).
Anyway I'm sure this could fit your linqness
requirements.
EDIT: Alternatively this is the less words version (it doesn't make use of Count method):
public static IEnumerable<IEnumerable<T>> Consecutives<T>(this IEnumerable<T> numbers, int ranges, Func<T, bool> predicate)
{
var ordered = numbers.OrderBy(a => a);
return ordered.Where(predicate)
.Select((element, i) => ordered.Skip(i * ranges).Take(ranges))
.TakeWhile(Enumerable.Any);
}
I had to do this for a list of doubles. There is an upper as well as a lower limit. This is also not a true Linq solution, it is just a pragmatic approach I wrote this in scripting language that only implements a subset of C#.
var sequence =
[0.25,0.5,0.5,0.5,0.7,0.8,0.7,0.9,0.5,0.5,0.8,0.8,0.5,0.5,0.65,0.65,0.65,0.65,0.65,0.65,0.65];
double lowerLimit = 0.1;
double upperLimit = 0.6;
int minWindowLength = 3;
// return type is a list of lists
var windows = [[0.0]];
windows.Clear();
int consec = 0;
int index = 0;
while (index < sequence.Count){
// store segments here
var window = new System.Collections.Generic.List<double>();
while ((index < sequence.Count) && (sequence[index] > upperLimit || sequence[index] < lowerLimit)) {
window.Add(sequence[index]);
consec = consec + 1;
index = index +1;
}
if (consec > minWindowLength) {
windows.Add(window);
}
window = new System.Collections.Generic.List<double>();
consec = 0;
index = index+1;
}
return windows;