Меня поражает, что первое, что вы должны сделать, это заказать список.Тогда нужно просто пройтись по ней, запомнить длину текущей последовательности и определить, когда она закончилась.Честно говоря, я подозреваю , что простой цикл foreach будет самым простым способом сделать это - я не могу сразу думать о каких-либо удивительно изящных LINQ-подобных способах сделать это.Вы, конечно, можете сделать это в блоке итератора, если вы действительно этого хотите, но имейте в виду, что упорядочение списка для начала означает, что вы все равно получите разумную «предварительную» стоимость.Таким образом, мое решение выглядело бы примерно так:
var ordered = list.OrderBy(x => x);
int count = 0;
int firstItem = 0; // Irrelevant to start with
foreach (int x in ordered)
{
// First value in the ordered list: start of a sequence
if (count == 0)
{
firstItem = x;
count = 1;
}
// Skip duplicate values
else if (x == firstItem + count - 1)
{
// No need to do anything
}
// New value contributes to sequence
else if (x == firstItem + count)
{
count++;
}
// End of one sequence, start of another
else
{
if (count >= 3)
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
count, firstItem);
}
count = 1;
firstItem = x;
}
}
if (count >= 3)
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
count, firstItem);
}
РЕДАКТИРОВАТЬ: Хорошо, я только что подумал о несколько более LINQ-иш способ ведения дел.У меня нет времени, чтобы полностью реализовать это сейчас, но:
- Заказать последовательность
- Использовать что-то вроде
SelectWithPrevious
(возможно, лучше назвать SelectConsecutive
) для получения последовательных пар элементов - Используйте перегрузку Select, которая включает в себя индекс для получения кортежей (index, current, previous)
- Отфильтруйте любые элементы, где (current = previous+ 1) получить где-нибудь, что считается началом последовательности (индекс особого случая = 0)
- Используйте
SelectWithPrevious
в результате, чтобы получить длину последовательности между двумя начальными точками (вычтите один индекс из предыдущей) - Отфильтруйте любую последовательность длиной менее 3
I подозреваемый вам нужноконкатить int.MinValue
в упорядоченной последовательности, чтобы гарантировать правильное использование конечного элемента.
РЕДАКТИРОВАТЬ: Хорошо, я реализовал это.Речь идет о самом ЛИНКОМ способе, которым я могу придумать, чтобы сделать это ... Я использовал нулевые значения в качестве «дозорных» значений для принудительного начала и окончания последовательности - см. Комментарии для более подробной информации.
В целом, я бы не рекомендовалэто решение.Трудно разобраться, и хотя я достаточно уверен, что это правильно, мне потребовалось некоторое время, чтобы подумать о возможных ошибках и т. Д. Это интересное путешествие в то, что вы можете делать сLINQ ... а также то, что вы, вероятно, не должны.
О, и обратите внимание, что я выдвинул часть "минимальная длина 3" до вызывающей стороны - когда у вас есть последовательность кортежей, как этоЧистее отфильтровывать отдельно, ИМО.
using System;
using System.Collections.Generic;
using System.Linq;
static class Extensions
{
public static IEnumerable<TResult> SelectConsecutive<TSource, TResult>
(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
using (IEnumerator<TSource> iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
yield break;
}
TSource prev = iterator.Current;
while (iterator.MoveNext())
{
TSource current = iterator.Current;
yield return selector(prev, current);
prev = current;
}
}
}
}
class Test
{
static void Main()
{
var list = new List<int> { 21,4,7,9,12,22,17,8,2,20,23 };
foreach (var sequence in FindSequences(list).Where(x => x.Item1 >= 3))
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
sequence.Item1, sequence.Item2);
}
}
private static readonly int?[] End = { null };
// Each tuple in the returned sequence is (length, first element)
public static IEnumerable<Tuple<int, int>> FindSequences
(IEnumerable<int> input)
{
// Use null values at the start and end of the ordered sequence
// so that the first pair always starts a new sequence starting
// with the lowest actual element, and the final pair always
// starts a new one starting with null. That "sequence at the end"
// is used to compute the length of the *real* final element.
return End.Concat(input.OrderBy(x => x)
.Select(x => (int?) x))
.Concat(End)
// Work out consecutive pairs of items
.SelectConsecutive((x, y) => Tuple.Create(x, y))
// Remove duplicates
.Where(z => z.Item1 != z.Item2)
// Keep the index so we can tell sequence length
.Select((z, index) => new { z, index })
// Find sequence starting points
.Where(both => both.z.Item2 != both.z.Item1 + 1)
.SelectConsecutive((start1, start2) =>
Tuple.Create(start2.index - start1.index,
start1.z.Item2.Value));
}
}