说我有一个整数列表:
List<int> myInts = new List<int>() {1,2,3,5,8,13,21};
我想获得下一个可用的整数,按增加整数排序。不是最后一个或最高的,但在这种情况下,下一个不在此列表中的整数。在这种情况下,数字是4。
是否有LINQ语句会给我这个?如:
var nextAvailable = myInts.SomeCoolLinqMethod();
编辑:废话。我说答案应该是2,但我的意思是4.我为此道歉!
例如:想象一下,您负责分发流程ID。您想获取当前进程ID的列表,并发出下一个进程ID,但下一个进程ID不应该只是最高值加1。相反,它应该是从有序的进程ID列表中可用的下一个。你可以从最高的开始获得下一个可用的,这并不重要。
答案 0 :(得分:25)
我看到很多编写自定义扩展方法的答案,但是可以使用标准的linq扩展方法和静态Enumerable类来解决这个问题:
List<int> myInts = new List<int>() {1,2,3,5,8,13,21};
// This will set firstAvailable to 4.
int firstAvailable = Enumerable.Range(1, Int32.MaxValue).Except(myInts).First();
答案 1 :(得分:3)
@Kevin提供的答案有不良的表现形象。逻辑将多次访问源序列:一次用于.Count
呼叫,一次用于.FirstOrDefault
呼叫,一次用于每个.Contains
呼叫。如果IEnumerable<int>
实例是延迟序列,例如.Select
调用的结果,则这将导致序列的至少2次计算,以及每个数字的一次计算。即使您将列表传递给方法,它也可能会遍历每个选中号码的整个列表。想象一下,在序列{ 1, 1000000 }
上运行它,你可以看到它不会表现得如何。
LINQ努力迭代源序列不超过一次。这通常是可能的,并且会对代码的性能产生很大影响。下面是一个扩展方法,它将迭代序列一次。它是通过查找每个连续对之间的差异来实现的,然后将第一个较低的数字加1,这比下一个数字多1:
public static int? FirstMissing(this IEnumerable<int> numbers)
{
int? priorNumber = null;
foreach(var number in numbers.OrderBy(n => n))
{
var difference = number - priorNumber;
if(difference != null && difference > 1)
{
return priorNumber + 1;
}
priorNumber = number;
}
return priorNumber == null ? (int?) null : priorNumber + 1;
}
由于可以在任意整数序列上调用此扩展方法,因此我们确保在迭代之前对它们进行排序。然后我们计算当前数字和前一个数字之间的差异。如果这是列表中的第一个数字,priorNumber
将为空,因此difference
将为空。如果这不是列表中的第一个数字,我们会检查与前一个数字的差异是否正好为1.如果不是,我们知道存在差距,我们可以将前一个数字加1。
您可以根据需要调整return语句以处理包含0或1项的序列;我选择为空序列返回null,为序列{ n }
返回n + 1。
答案 2 :(得分:2)
这将非常有效:
static int Next(this IEnumerable<int> source)
{
int? last = null;
foreach (var next in source.OrderBy(_ => _))
{
if (last.HasValue && last.Value + 1 != next)
{
return last.Value + 1;
}
last = next;
}
return last.HasValue ? last.Value + 1 : Int32.MaxValue;
}
答案 3 :(得分:0)
public static class IntExtensions
{
public static int? SomeCoolLinqMethod(this IEnumerable<int> ints)
{
int counter = ints.Count() > 0 ? ints.First() : -1;
while (counter < int.MaxValue)
{
if (!ints.Contains(++counter)) return counter;
}
return null;
}
}
用法:
var nextAvailable = myInts.SomeCoolLinqMethod();
答案 4 :(得分:0)
好的,我提出的解决方案对我有用。
var nextAvailableInteger = Enumerable.Range(myInts.Min(),myInts.Max()).FirstOrDefault( r=> !myInts.Contains(r));
如果有人有更优雅的解决方案,我很乐意接受这一点。但就目前而言,这正是我在我的代码中所做的并继续前进。
编辑:这是我在Kevin建议添加扩展方法后实现的。这才是真正的答案 - 没有一个LINQ扩展会这样做,所以添加我自己的更有意义。这就是我真正想要的。
public static int NextAvailableInteger(this IEnumerable<int> ints)
{
return NextAvailableInteger(ints, 1); // by default we use one
}
public static int NextAvailableInteger(this IEnumerable<int> ints, int defaultValue)
{
if (ints == null || ints.Count() == 0) return defaultValue;
var ordered = ints.OrderBy(v => v);
int counter = ints.Min();
int max = ints.Max();
while (counter < max)
{
if (!ordered.Contains(++counter)) return counter;
}
return (++counter);
}
答案 5 :(得分:0)
不确定这是否符合酷Linq方法,但使用This SO Answer
中的左外连接理念var thelist = new List<int> {1,2,3,4,5,100,101};
var nextAvailable = (from curr in thelist
join next in thelist
on curr + 1 equals next into g
from newlist in g.DefaultIfEmpty()
where !g.Any ()
orderby curr
select curr + 1).First();
如果您使用Linq to Sql,这会将处理放在sql server端,并且您不必将ID列表从服务器拉到内存。
答案 6 :(得分:0)
var nextAvailable = myInts.Prepend(0).TakeWhile((x,i) => x == i).Last() + 1;
已经7年了,但是比选择的答案或获得最高票数的答案更好的方法。
该列表已经按顺序排列,并且根据示例0不计算在内。我们可以在0之前加上前缀,然后检查每个项目是否与其索引匹配。当TakeWhile遇到不匹配的数字或在列表的末尾时,它将停止评估。
答案是匹配的最后一项,再加上1。
TakeWhile比枚举所有可能的数字然后使用Except排除现有数字更有效,因为我们的TakeWhile只会遍历列表,直到找到第一个可用数字,并且得到的Enumerable集合最多为n。
使用Except的答案会生成一个完整的,可枚举的答案,而这些答案并不仅仅是抓住第一个答案。 Linq可以使用First()进行一些优化,但与TakeWhile相比,它仍然慢得多,并且占用的内存也更多。