【问题标题】:get next available integer using LINQ使用 LINQ 获取下一个可用整数
【发布时间】:2012-06-19 01:44:51
【问题描述】:

假设我有一个整数列表:

List<int> myInts = new List<int>() {1,2,3,5,8,13,21};

我想获取下一个可用整数,按整数递增排序。不是最后一个或最高的一个,但在这种情况下是不在此列表中的下一个整数。在这种情况下,数字是 4。

有没有 LINQ 语句可以给我这个?如:

var nextAvailable = myInts.SomeCoolLinqMethod();

编辑:废话。我说答案应该是 2,但我的意思是 4。对此我深表歉意!

例如:假设您负责分发进程 ID。您想获取当前进程 ID 的列表,并发出下一个,但下一个不应只是最大值加一。相反,它应该是进程 ID 有序列表中的下一个可用的。您可以从最高的开始获得下一个可用的,这并不重要。

【问题讨论】:

  • next available 。参考什么?
  • 我认为您正在寻找有序列表
  • 接下来关于递增的整数。所以是的,这是一个有序列表,所以整数比较是相关的。
  • 要订购列表,请使用 myInts OrderBy(d =&gt; d)。如果已订购,myInts[0] 将是较小的数字,因此 myInts[1] 将是 nextAvailable

标签: c# linq linq-to-objects


【解决方案1】:

我看到很多写自定义扩展方法的答案,但是使用标准的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();

【讨论】:

  • 发现了一个错字,但无法更改原因,因为更改的字符较少;-) 可用 => 可用
【解决方案2】:

@Kevin 提供的答案具有不良的性能特征。该逻辑将多次访问源序列:一次用于.Count 调用,一次用于.FirstOrDefault 调用,一次用于每个.Contains 调用。如果IEnumerable&lt;int&gt; 实例是延迟序列,例如.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。

【讨论】:

    【解决方案3】:

    这将是相当有效的:

    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;
    }
    

    【讨论】:

      【解决方案4】:
      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();
      

      【讨论】:

      • 谢谢,这基本上是我在本地 int 扩展类中实现的。我不确定是否已经有一些东西可以做到这一点,但使用这种方法至少在本地有一个。
      • 这种方法的最大问题是当ints 不重要或来自数据库时效率低。
      • 但这不是我试图解决的问题。优化最好留到主逻辑到位之后,并且只有在需要时才进行。
      【解决方案5】:

      好的,这是我想出的适合我的解决方案。

      var nextAvailableInteger = Enumerable.Range(myInts.Min(),myInts.Max()).FirstOrDefault( r=> !myInts.Contains(r));
      

      如果有人有更优雅的解决方案,我很乐意接受。但就目前而言,这就是我在代码中添加的内容并继续前进。

      编辑:这是我在 Kevin 建议添加扩展方法后实现的。这才是真正的答案——没有一个 LINQ 扩展会这样做,所以添加我自己的扩展会更有意义。这正是我一直在寻找的。​​p>

      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);
      }
      

      【讨论】:

      • 如果您使用的是 LINQ-to-Objects,您可能不会注意到任何事情。如果您使用的是 LINQ-to-SQL,这将非常糟糕。您正在调用多个 LINQ 函数,所有这些函数都可能需要遍历整个枚举!你也已经有了有序列表,所以最小值是第一个,最大值是最后一个。
      • 这仅适用于 Linq to Objects。不过还是谢谢。
      【解决方案6】:

      不确定这是否是一种很酷的 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 服务器端,并且允许您不必将 ID 列表从服务器拉到内存。

      【讨论】:

        【解决方案7】:
        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 慢得多且占用更多内存。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-04
          相关资源
          最近更新 更多