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

时间:2013-07-02 16:42:40

标签: 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;
}

4 个答案:

答案 0 :(得分:2)

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

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;

答案 1 :(得分:2)

改进其他答案,请使用Where的备用版本。

int[] existingNumbers = ...
var result = existingNumbers.Where( (x,i) => x != i ).FirstOrDefault();

i是从0开始的计数器。

.NET 3.5(http://msdn.microsoft.com/en-us/library/bb549418(v=vs.90).aspx)支持此版本的where

答案 2 :(得分:0)

var firstnonexistingfile = Enumerable.Range(0,999999).Select(x => String.Format("Image_{0:D6}.{1}", x, strExt)).FirstOrDefault(x => !File.Exists(x));

这将从0迭代到999999,然后将String.Format()的结果输出为IEnumerable<string>,然后找到返回false的该序列中的第一个字符串File.Exists()

答案 3 :(得分:0)

这是一个老问题,但有人建议(在评论中)您可以使用.Except()代替。我倾向于喜欢这个解决方案,因为它会给你第一个缺失的数字(间隙)或序列中的下一个最小数字。这是一个例子:

var allNumbers = Enumerable.Range(0, 999999); //999999 is arbitrary. You could use int.MaxValue, but it would degrade performance
var existingNumbers = new int[] { 0, 1, 2, 4, 5, 6 };

int result;
var missingNumbers = allNumbers.Except(existingNumbers);
if (missingNumbers.Any())
  result = missingNumbers.First();
else //no missing numbers -- you've reached the max
  result = -1;

运行上面的代码会将result设置为:

3

此外,如果您将existingNumbers更改为:

var existingNumbers = new int[] { 0, 1, 3, 2, 4, 5, 6 };

所以没有差距,你会得到7分。

无论如何,这就是为什么我更喜欢除了Zip解决方案 - 只是我的两分钱。 谢谢!