Linq to Find Series of Consecutive Numbers

LINQ to find series of consecutive numbers

A linqish way can be writing an extension method GroupWhile like below (All checks omitted. not optimized to understand easily.)

int[] list = new int[] { 1, 2, 3, 5, 7, 8 };
var result = list.GroupWhile((x, y) => y - x == 1)
.Select(x => new {i = x.First(), len = x.Count() })
.ToList();

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> seq, Func<T,T,bool> condition)
{
T prev = seq.First();
List<T> list = new List<T>() { prev };

foreach(T item in seq.Skip(1))
{
if(condition(prev,item)==false)
{
yield return list;
list = new List<T>();
}
list.Add(item);
prev = item;
}

yield return list;
}

TODO: use IGrouping :)

Finding Consecutive Items in List using Linq

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>();
}
}
}

Linq - getting consecutive numbers in an array

Stupid is as stipid does! Whay was I so worried about using LINQ when the quickest soulution was a simple bit mask:

    List<Card> cardList = new List<Card>();
cardList.Add(new Card { Suit = SUITS.Diamonds, Val = RANK.Two });
cardList.Add(new Card { Suit = SUITS.Hearts, Val = RANK.Three });
cardList.Add(new Card { Suit = SUITS.Clubs, Val = RANK.Five });
cardList.Add(new Card { Suit = SUITS.Diamonds, Val = RANK.Seven });
cardList.Add(new Card { Suit = SUITS.Hearts, Val = RANK.Four });
cardList.Add(new Card { Suit = SUITS.Clubs, Val = RANK.King });
cardList.Add(new Card { Suit = SUITS.Diamonds, Val = RANK.Ace });

int card = 0;
foreach (Card c in cardList)
{
card |= 1 << (int)c.Val - 1;
}

bool isStraight = false;
RANK high = RANK.Five;
int mask = 0x1F;
while (mask < card)
{
++high;

if ((mask & card) == mask)
{
isStraight = true;
}
else if (isStraight)
{
--high;
break;
}

mask <<= 1;
}

// Check for Ace low
if ((!isStraight) && ((0x100F & card) == 0x100F))
{
isStraight = true;
high = RANK.Five;
}

return card;

Consecutive elements with consecutive sum greater than 0 using Linq

If you HAVE to use Linq, and you can sort of cheat and use MoreLinq, then this ugly mess would work (it gives back multiple collections since you could in theory have multiple runs of the same max size)

Enumerable.Range(0, input.Count())
.Select(i =>
input.Skip(i)
.Scan((Current: 0, Total: 0), (x, y) => (y, x.Total + y))
.Skip(1)
.TakeWhile(x => x.Total >= 0))
.MaxBy(x => x.Count())
.Select(x => x.Select(y => y.Current));

C# & LINQ, Select two (consecutive) items at once

Another answer presents a nice and clean solution using LINQ's Skip and Zip.

It is absolutely correct, but I'd like to point out that it enumerates the source twice. That may or may not matter, depending on each individual use case. If it matters for your case, here's a longer alternative that is functionally equivalent but enumerates the source once:

static class EnumerableUtilities
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (selector == null) throw new ArgumentNullException(nameof(selector));

return SelectTwoImpl(source, selector);
}

private static IEnumerable<TResult> SelectTwoImpl<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
using (var iterator = source.GetEnumerator())
{
var item2 = default(TSource);
var i = 0;
while (iterator.MoveNext())
{
var item1 = item2;
item2 = iterator.Current;
i++;

if (i >= 2)
{
yield return selector(item1, item2);
}
}
}
}
}

Example:

var seq = new[] {"A", "B", "C", "D"}.SelectTwo((a, b) => a + b);

The resulting sequence contains "AB", "BC", "CD".

How to find consecutive same values items as a Linq group

What you're looking for here is a GroupWhile<T> method.

Credit to user L.B for the solution. Go give his original answer an UpDoot
https://stackoverflow.com/a/20469961/30155

    var schedules = new List<Item>{
new Item { Id=1, Name = "S" },
new Item { Id=2, Name = "P" },
new Item { Id=3, Name = "X" },
new Item { Id=4, Name = "X" },
new Item { Id=5, Name = "P" },
new Item { Id=6, Name = "P" },
new Item { Id=7, Name = "P" },
new Item { Id=8, Name = "S" }
};

var results = schedules
.GroupWhile((preceding, next) => preceding.Name == next.Name)
//Group items, while the next is equal to the preceding one
.Where(s => s.Count() > 1)
//Only include results where the generated sublist have more than 1 element.
.ToList();

foreach (var sublist in results)
{
foreach (Item i in sublist)
{
Console.WriteLine($"{i.Name} - {i.Id}");
}
Console.WriteLine("");
}

Console.ReadLine();

You can add the implementation as an Extension Method to all IEnumerable<T> like so.

public static class Extensions
{
public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> seq, Func<T, T, bool> condition)
{
T prev = seq.First();
List<T> list = new List<T>() { prev };

foreach (T item in seq.Skip(1))
{
if (condition(prev, item) == false)
{
yield return list;
list = new List<T>();
}
list.Add(item);
prev = item;
}

yield return list;
}
}

Selecting Consecutive String Entries with LINQ to Entities

1- If you are aware of performance side effect of calling AsEnumerable() cast your query and do conversion in memory on the retrieved entities.

2- If you don't want solution #1, you have to look for a way to solve the conversion problem:

2-1- Either change the column type in the database to int

2-2- Or select one of the solution previously proposed by other developers such as:

Problem with converting int to string in Linq to entities

Use LINQ to group a sequence of numbers with no gaps

var array = new int[] { 1, 2, 3, 4, 7, 8, 11, 15, 16, 17, 18 };

var result = string.Join(",", array
.Distinct()
.OrderBy(x => x)
.GroupAdjacentBy((x, y) => x + 1 == y)
.Select(g => new int[] { g.First(), g.Last() }.Distinct())
.Select(g => string.Join("-", g)));

with

public static class LinqExtensions
{
public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { e.Current };
var pred = e.Current;
while (e.MoveNext())
{
if (predicate(pred, e.Current))
{
list.Add(e.Current);
}
else
{
yield return list;
list = new List<T> { e.Current };
}
pred = e.Current;
}
yield return list;
}
}
}
}


Related Topics



Leave a reply



Submit