Linq是否提供了一种方法来轻松发现序列中的间隙
本文关键字:发现 间隙 方法 一种 是否 Linq | 更新日期: 2023-09-27 18:20:54
我正在管理一个文件目录。每个文件的名称将类似于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;
}
EDIT:如果它对任何人都有帮助,这里是最后的方法,结合了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();
如果已使用所有数字且不存在空白,则返回1000000。
这种方法的缺点是它执行得相当糟糕,因为它多次迭代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;
与其他答案相比,使用Where
的备用版本。
int[] existingNumbers = ...
var result = existingNumbers.Where( (x,i) => x != i ).FirstOrDefault();
值i
是从0
开始的计数器。
.NET 3.5支持此版本的where
(http://msdn.microsoft.com/en-us/library/bb549418(v=vs.90).aspx).
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>
,然后找到该序列中第一个为File.Exists()
返回false的字符串。
这是一个老问题,但(在评论中)建议您可以使用.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
此外,如果您将现有号码更改为:
var existingNumbers = new int[] { 0, 1, 3, 2, 4, 5, 6 };
所以没有差距,你会得到7分。
不管怎样,这就是为什么我更喜欢除了Zip解决方案——只有我的两美分。谢谢