C# 找到“最有效的方法”;“空间”;在使用LINQ的有序列表中

C# 找到“最有效的方法”;“空间”;在使用LINQ的有序列表中,c#,algorithm,linq,time-complexity,C#,Algorithm,Linq,Time Complexity,我想做的是,如果遍历 IEnumerable<int> ids = items.Select(item => item.Id); IEnumerable Id=items.Select(item=>item.Id); 这是0157 然后我想选择2,因为这是[0,7+1]范围内最小的I,因此I-1在列表中,而I不在列表中。如果0不在列表中,则会选择0 最简洁、高效、可读、新奇、最灵巧的方法是什么 如果需要更详细的解释,请告诉我。对于非空集合,这可能是最短的实现,它是O(N)

我想做的是,如果遍历

IEnumerable<int> ids = items.Select(item => item.Id); 
IEnumerable Id=items.Select(item=>item.Id);
这是
0157

然后我想选择
2
,因为这是
[0,7+1]
范围内最小的
I
,因此
I-1
在列表中,而
I
不在列表中。如果
0
不在列表中,则会选择
0


最简洁、高效、可读、新奇、最灵巧的方法是什么


如果需要更详细的解释,请告诉我。

对于非空集合,这可能是最短的实现,它是
O(N)
(它还假设您拥有的
Id
字段是数字,但不会改变任何内容):

工作原理:它在列表上迭代,直到值与其索引匹配。然后在某个值与其索引不匹配之后-是一个间隙

UPD

在的帮助下,它修复了处理以非零开头的列表的问题:

var missingIndex = list.TakeWhile((v, i) => v == i).Select(i => i + 1).LastOrDefault();

为了获得高效的解决方案,您需要使用二进制搜索,这在命令式样式中看起来更好(请参见non-linq'y)。

编写扩展方法来实现这一点并不困难

    //examples
    class Program
    {
        static void Main(string[] args)
        {
            //Your original example
            var items = new List<int> { 0, 1, 5, 7 };
            var gap = items.FindFirstGap();
            Console.WriteLine(gap); //shows 2

            //No gaps
            items = new List<int> { 0, 1, 2, 3 };
            gap = items.FindFirstGap();
            Console.WriteLine(gap); //shows 4

            //no 0
            items = new List<int> { 1, 2, 3, 4 };
            gap = items.FindFirstGap();
            Console.WriteLine(gap); //shows 0

            Console.ReadLine();
        }
    }
以下是我的看法:

    private static int GetGap(int[] items)
    {
        // return items.TakeWhile((v, i) => v == i).Select(i => i + 1).LastOrDefault();
        if (!items.Any()) return 0;
        if (items.First() != 0) return 0;
        int counter = 0;
        return items.ToList().LastOrDefault(x => x == counter++) + 1;
    }
测试:

   public static IEnumerable TestCases
    {
        get
        {
            yield return new TestCaseData(new int[] { 0, 1, 5, 7 }, 2).SetName("1");
            yield return new TestCaseData(new int[] { 0, 1, 2, 3, 4 },5).SetName("2");
            yield return new TestCaseData(new int[] { 1, 2, 3, 4 },0).SetName("3");
            yield return new TestCaseData(new int[] { -2, -1, 1, 2, 3, 4 }, 0).SetName("4");
            yield return new TestCaseData(new int[] { -2, -1, 0, 1, 2, 3, 4 },0).SetName("5");
            yield return new TestCaseData(new int[] { 0 },1).SetName("6");
            yield return new TestCaseData(new int[] { },0).SetName("7");

        }
    }

    [TestCaseSource("TestCases")]
    public void Test(int[] items, int expected ) {
        Assert.AreEqual(expected, GetGap(items));
    }

为了找到第一个缺口的索引, 如果符合您的要求,请尝试一下

 int[] items = new int[] { 0,1, 3, 4, 6 }; 
 var firstGap = items.Select((index, value) => new { value, index })
                     .FirstOrDefault(c => c.value == 0 ? items[c.value] != 0 : items[c.value] != items[c.value - 1] + 1).value; 

您需要给出更详细的解释。@Abion47基本上,我是在按顺序查找插入点,这样您就不会试图查找
2
并查看它进入哪个索引了?你只想找到第一个有缺口的索引?“紧凑、高效、可读、新奇、最灵巧”——选择一个。我已经给出了几个可能的答案,但显然这个问题页面正在被快乐的小仙女访问,她们在没有解释的情况下投了反对票,因此,祝您和您的问题好运。用户看到的代码是
items.FindFirstGap()(请参阅代码底部的
Main()
函数了解3个示例用法)其余代码可以隐藏在库中。你唯一需要做的就是为
long
short
byte
添加重载,如果你想自己编写比较器,还可以添加
float
double
。“剩下的代码可以放在库中。”---然后进行二进制搜索是有意义的。@zerkms我一开始也是这么想的,但是除非在开始搜索之前有一个目标号码,否则二进制搜索是不起作用的。如果你有办法的话,请发布它,因为我不知道你将如何实现它。@ScottChamberlain这很简单-与我线性搜索的方法相同,但使用二进制搜索:你取中间元素,并将其值与它的索引进行比较。如果它们匹配-你向右移动,否则你向左移动。@zerkms实际上我想如果你只使用
int
s,你可以进行二进制搜索,比较
I==value[I]
,如果不相等,则进行二进制搜索,直到找到最大的值为止。编辑:看起来你发布了正确的答案,因为我自己也找到了答案。只有当列表中有一个元素的值为
0
时,这才有效。否则,
TakeWhile
返回一个空集合,
Last
将抛出异常。@Abion47这太不幸了!我将思考一分钟或删除答案:-(@Abion47
ids.TakeWhile((v,I)=>v==I)。选择(I=>I+1)。LastOrDefault()
修复了我想提及的另一件事,即此方法假设集合已排序。@zerkms在这种情况下,我将图片作为编辑放在随机帖子中,将链接复制到imgur SO生成,然后取消编辑,然后在注释中发布链接。这样,您就不必管理图片的来源。
   public static IEnumerable TestCases
    {
        get
        {
            yield return new TestCaseData(new int[] { 0, 1, 5, 7 }, 2).SetName("1");
            yield return new TestCaseData(new int[] { 0, 1, 2, 3, 4 },5).SetName("2");
            yield return new TestCaseData(new int[] { 1, 2, 3, 4 },0).SetName("3");
            yield return new TestCaseData(new int[] { -2, -1, 1, 2, 3, 4 }, 0).SetName("4");
            yield return new TestCaseData(new int[] { -2, -1, 0, 1, 2, 3, 4 },0).SetName("5");
            yield return new TestCaseData(new int[] { 0 },1).SetName("6");
            yield return new TestCaseData(new int[] { },0).SetName("7");

        }
    }

    [TestCaseSource("TestCases")]
    public void Test(int[] items, int expected ) {
        Assert.AreEqual(expected, GetGap(items));
    }
 int[] items = new int[] { 0,1, 3, 4, 6 }; 
 var firstGap = items.Select((index, value) => new { value, index })
                     .FirstOrDefault(c => c.value == 0 ? items[c.value] != 0 : items[c.value] != items[c.value - 1] + 1).value;