在有序序列中获得第一个缺失元素的有效方法?

时间:2009-07-08 14:54:42

标签: c# linq linq-to-sql search

我有一个有序的序列,如{1,3,5,6,8,9}我想得到第一个缺失元素(示例中为2)或max()如果序列不包含缺失元素。 现在我这样做:

public static int GetRegisterNumber<T>(this IQueryable<T> enumerable, Func<T, bool> whereFunc, Func<T, int?> selectFunc)
{
    var regNums = enumerable.OrderBy(selectFunc).Where(whereFunc).ToArray();

    if (regNums.Count() == 0)
    {
        return 1;
    }

    for (int i = 0; i < regNums.Count(); i++)
    {
        if (i + 1 != regNums[i])
        {
            return regNums[i].Value + 1;
        }
    }

    return regNums.Last().Value + 1;
}

但我认为有更快的方法。有什么建议吗?

8 个答案:

答案 0 :(得分:6)

修改:我刚发现enumerableIQueryable<T>,但selectFuncwhereFunc的类型为Func<T, _>。这将导致调用EnumerableOrderBy的{​​{1}}版本,而不是使用数据库调用。您可能希望将它们切换为Where

如果您不想先订购Expression<Func<T, _>>,这是一个O(n)高尔夫风格的解决方案:

regNums

按行:

  1. 通过转换为var max = regNums.Max(i => (int?)i) ?? 0; return Enumerable.Range(1, max + 1) .Except(regNums) .Min(); ,如果int?为空,则Max将返回null,并合并为regNums

  2. 构建所有可能寄存器的序列,包括我们的下一个值(如果已满)。

  3. 减去当前的寄存器组。

  4. 选择最低的。

答案 1 :(得分:5)

我可能会看下面的东西; Where可以在外面完成(诚实的选择也是如此):

如果您希望从1开始:

public static int GetRegisterNumber<T>(this IEnumerable<T> enumerable,
    Func<T, int> selectFunc)
{
    int expected = 1;
    foreach (T item in enumerable) {
        if (selectFunc(item) != expected) return expected;
        expected++;
    }
    return expected;
}

从列表中的第一项开始:

public static int GetRegisterNumber<T>(this IEnumerable<T> enumerable,
    Func<T, int> selectFunc)
{
    bool first = true;
    int prev = -1;
    foreach (T item in enumerable)
    {
        int val = selectFunc(item);
        if(first) {
            prev = val;
            first = false;
        } else if (val != prev + 1) {
            return prev + 1;
        }
        prev = val;
    }
    return first ? 1 : prev + 1;
}

目前尚不清楚你想如何处理空值,所以我没有。请注意,这只会迭代一次,并且不会缓冲所有内容。

答案 2 :(得分:5)

假设您已应用OrderByWhere

int firstMissing = collection.TakeWhile((x, i) => x == ++i).LastOrDefault() + 1;

答案 3 :(得分:4)

建议:通过分析器运行代码。然后你会知道它在哪里慢。直观地说,OrderBy是这个程序中最慢的东西。但关于最慢的东西的直觉往往是非常非常错误的。使用分析器。

当然,您还应该消除此计划中的大量低效率。请记住,Count()通过重新枚举它来计算序列。自上次计数以来,Count()不知道您没有更改序列!您可能希望存储计数而不是每次都重新计算它,或者使用Length,因为您有一个数组。

答案 4 :(得分:4)

为什么不做二分搜索这样的事情?

假设您有一个列表10个元素长。阅读第一个元素。然后阅读第五个元素。如果第五个元素不是第一个元素+4那么你知道有一个缺失的数字,否则你知道没有。然后像这样迭代,直到找到第一个缺失的元素,或者到达列表的末尾。

这当然假设您知道大小(问题中未明确提及),但您已转换为数组,因此您应该知道。

O(log N)而不是O(n)

答案 5 :(得分:1)

假设传入的值序列已经排序,那么:

var upperBoundValue = values.Last() + 1;
var firstMissingItem = Enumerable.Range(1, upperBoundValue).Except(values).First();

如果您正在迭代地执行此操作,则可以通过将索引存储到您在找到间隙的序列中的最后位置来优化该过程,并从那里开始下一次迭代。

答案 6 :(得分:0)

如上所述,首先使用分析器来查找它的慢速位置。如果序列非常大,并且排序很慢,则可以使用radix sort,即O(kn),其中k是最大位数,n是序列中元素的数量。基于比较的排序算法通常为O(n logn)。

这样整个算法将是O(kn),它取决于n,渐近更快,因此更具可扩展性。

答案 7 :(得分:0)

您在问题中添加了LinqToSql标记。我认为您正在寻找“第一个可用”ID,以便您可以使用此ID创建新记录。请考虑改为在数据库中启用IDENTITY。