Dve*_*Dve 22 c# logic sequences
我有一个数字列表,例如21,4,7,9,12,22,17,8,2,20,23
我希望能够选出序列号序列(最少3个项目),所以从上面的例子可以看出它是7,8,9和20,21,22,23.
我玩了一些丑陋的庞大功能,但我想知道是否有一个简洁的LINQ-ish方法来做到这一点.
有什么建议?
更新:
非常感谢所有的回应,非常感谢.我现在正和他们一起玩,看看哪个最适合我们的项目.
Jon*_*eet 27
我觉得你应该做的第一件事是订购清单.然后只需要走过它,记住当前序列的长度并检测它何时结束.说实话,我怀疑一个简单的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);
}
Run Code Online (Sandbox Code Playgroud)
编辑:好的,我刚刚想到了一种更加LINQ-ish的做事方式.我现在没有时间全面实施它,但是:
SelectWithPrevious(可能更好的命名SelectConsecutive)的东西来获得连续的元素对SelectWithPrevious在结果上使用以获得两个起点之间的序列长度(从前一个减去一个索引)我怀疑你需要int.MinValue在有序序列上连续,以保证最终项目正确使用.
编辑:好的,我实现了这一点.这是关于我能想到的LINQiest方法...我使用null值作为"sentinel"值来强制开始和结束序列 - 有关更多详细信息,请参阅注释.
总的来说,我不推荐这个解决方案.很难让你的头脑清醒,虽然我有理由相信这是正确的,但我花了一段时间考虑可能的一对一错误等.这是一个有趣的旅程,你可以用LINQ做什么......还有什么你可能不应该.
哦,请注意我已经将"最小长度为3"的部分推送到调用者 - 当你有一系列像这样的元组时,单独过滤它是更清晰的,IMO.
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));
}
}
Run Code Online (Sandbox Code Playgroud)
Ani*_*Ani 12
Jon Skeet的/ Timwi的解决方案是可行的方法.
为了好玩,这是一个完成工作的LINQ查询(非常低效):
var sequences = input.Distinct()
.GroupBy(num => Enumerable.Range(num, int.MaxValue - num + 1)
.TakeWhile(input.Contains)
.Last()) //use the last member of the consecutive sequence as the key
.Where(seq => seq.Count() >= 3)
.Select(seq => seq.OrderBy(num => num)); // not necessary unless ordering is desirable inside each sequence.
Run Code Online (Sandbox Code Playgroud)
通过将输入加载到HashSet(改进Contains)中可以略微提高查询的性能,但这仍然不会产生任何接近有效的解决方案.
我所知道的唯一错误是如果序列包含大数量的负数(我们不能代表count参数Range),则算术溢出的可能性.使用自定义static IEnumerable<int> To(this int start, int end)扩展方法很容易解决这个问题.如果有人能想到任何其他避免溢出的简单技术,请告诉我.
编辑:这是一个稍微冗长(但同样效率低)的变种,没有溢出问题.
var sequences = input.GroupBy(num => input.Where(candidate => candidate >= num)
.OrderBy(candidate => candidate)
.TakeWhile((candidate, index) => candidate == num + index)
.Last())
.Where(seq => seq.Count() >= 3)
.Select(seq => seq.OrderBy(num => num));
Run Code Online (Sandbox Code Playgroud)