c# - Linq 是否提供了一种轻松发现序列中间隙的方法?

标签 c# linq .net-3.5 sequences

我正在管理一个文件目录。每个文件的命名方式与 Image_000000.png 类似,数字部分随存储的每个文件递增。

也可以删除文件,在数字序列中留下空白。我问的原因是因为我认识到在未来的某个时候,用户可能会用完数字序列,除非我采取措施在可用时重新使用数字。我意识到这是一百万,而且很多,但我们有 20 多年的用户,所以“总有一天”不是不可能的。

所以,我特别想问一下,是否有一种方法可以在不简单循环的情况下轻松确定序列中的间隙。我意识到因为它是一个固定范围,所以我可以简单地在预期范围内循环。

除非有更好/更清洁/更简单/更快的替代方案,否则我会的。如果是这样,我想知道。

调用此方法获取下一个可用文件名:

public static String GetNextImageFileName()
{
    String retFile = null;
    DirectoryInfo di = new DirectoryInfo(userVars.ImageDirectory);
    FileInfo[] fia = di.GetFiles("*.*", SearchOption.TopDirectoryOnly);
    String lastFile = fia.Where(i => i.Name.StartsWith("Image_") && i.Name.Substring(6, 6).ContainsOnlyDigits()).OrderBy(i => i.Name).Last().Name;
    if (!String.IsNullOrEmpty(lastFile))
    {
        Int32 num;
        String strNum = lastFile.Substring(6, 6);
        String strExt = lastFile.Substring(13);
        if (!String.IsNullOrEmpty(strNum) && 
            !String.IsNullOrEmpty(strExt) && 
            strNum.ContainsOnlyDigits() &&
            Int32.TryParse(strNum, out num))
        {
            num++;
            retFile = String.Format("Image_{0:D6}.{1}", num, strExt);
            while (num <= 999999 && File.Exists(retFile))
            {
                num++;
                retFile = String.Format("Image_{0:D6}.{1}", num, strExt);
            }
        }
    }

    return retFile;
}

编辑:如果对任何人有帮助,这里是最后一种方法,结合了 Daniel Hilgarth 的回答:

public static String GetNextImageFileName()
{
    DirectoryInfo di = new DirectoryInfo(userVars.ImageDirectory);
    FileInfo[] fia = di.GetFiles("Image_*.*", SearchOption.TopDirectoryOnly);
    List<Int32> fileNums = new List<Int32>();
    foreach (FileInfo fi in fia)
    {
        Int32 i;
        if (Int32.TryParse(fi.Name.Substring(6, 6), out i))
            fileNums.Add(i);
    }
    var result = fileNums.Select((x, i) => new { Index = i, Value = x })
                .Where(x => x.Index != x.Value)
                .Select(x => (Int32?)x.Index)
                .FirstOrDefault();

    Int32 index;
    if (result == null)
        index = fileNums.Count - 1;
    else
        index = result.Value - 1;

    var nextNumber = fileNums[index] + 1;

    if (nextNumber >= 0 && nextNumber <= 999999)
        return String.Format("Image_{0:D6}", result.Value);

    return null;
}

最佳答案

找到第一个间隙的第一个数字的一​​个非常简单的方法如下:

int[] existingNumbers = /* extract all numbers from all filenames and order them */
var allNumbers = Enumerable.Range(0, 1000000);
var result = allNumbers.Where(x => !existingNumbers.Contains(x)).First();

如果所有数字都已使用且不存在间隙,这将返回 1,000,000。

这种方法的缺点是性能很差,因为它多次迭代 existingNumbers

更好的方法是使用 Zip:

allNumbers.Zip(existingNumbers, (a, e) => new { Number = a, ExistingNumber = e })
          .Where(x => x.Number != x.ExistingNumber)
          .Select(x => x.Number)
          .First();

DuckMaestro 答案的改进版本实际上返回第一个间隙的第一个值——而不是第一个间隙之后的第一个值——看起来像这样:

var tmp = existingNumbers.Select((x, i) => new { Index = i, Value = x })
                         .Where(x => x.Index != x.Value)
                         .Select(x => (int?)x.Index)
                         .FirstOrDefault();

int index;
if(tmp == null)
    index = existingNumbers.Length - 1;
else
    index = tmp.Value - 1;

var nextNumber = existingNumbers[index] + 1;

关于c# - Linq 是否提供了一种轻松发现序列中间隙的方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17431142/

相关文章:

c# - 了解非 bool 参数之间的按位比较

asp.net - promote 方法仅在 azure 中为分布式事务返回无效值

c# - 在 Windows CE 中创建对象实例比反射更快

c# - 从 Visual Studio 2005 迁移到 2008 和 .NET 2.0

c# - DocumentDB 不支持的查询

c# - 从 .NET 3.5 中的 double 获取小数位的精确精度

c# - 使用 Azure Devops Rest API 的访问 token

c# - 有什么方法可以知道用户离开了 asp.net 的页面吗?

c# - 在方法中重写时的数组参数不会在其外部更改

c# - SQL select where但只检查参数是否为真