In case the directory with the screenshots contains many images, it might be beneficial to find the next available filename using binary search. This way the File.Exists
method will be called far fewer times than doing an incremental search.
/// <summary>
/// Performs a binary search in the Int32 range, and returns the first element
/// that satisfies a condition.
/// </summary>
public static TElement BinarySearchFirst<TElement>(
Func<int, TElement> selector,
Predicate<TElement> predicate,
int start = 1)
{
ArgumentNullException.ThrowIfNull(selector);
ArgumentNullException.ThrowIfNull(predicate);
if (start < 0) throw new ArgumentOutOfRangeException(nameof(start));
long lo = start;
long hi = 1;
(TElement Value, bool HasValue) maxFound = default;
// First stage, find an approximate upper limit of the search space.
while (hi < Int32.MaxValue)
{
hi = Math.Min(hi * 10, Int32.MaxValue);
if (hi < start) continue;
TElement item = selector((int)hi);
bool accepted = predicate(item);
if (accepted)
{
maxFound = (item, true);
hi--;
break;
}
lo = hi + 1;
}
// Second stage, perform binary search between lo and hi.
while (lo <= hi)
{
long mid = lo + ((hi - lo) >> 1);
TElement item = selector((int)mid);
bool accepted = predicate(item);
if (accepted)
{
maxFound = (item, true);
hi = mid - 1;
}
else
lo = mid + 1;
}
if (maxFound.HasValue) return maxFound.Value;
throw new InvalidOperationException("Element not found in the Int32 range.");
}
Usage example:
string pathFound = BinarySearchFirst(
i => Path.Combine(@"C:\Screenshots", $"Screenshot-{i}.png"),
path => !File.Exists(path));
In a folder with 200 screenshots, the above code will check for the existence of the files below:
Screenshot-10.png
Screenshot-100.png
Screenshot-1000.png
Screenshot-550.png
Screenshot-325.png
Screenshot-212.png
Screenshot-156.png
Screenshot-184.png
Screenshot-198.png
Screenshot-205.png
Screenshot-201.png
Screenshot-199.png
Screenshot-200.png
...before returning the value "C:\Screenshots\Screenshot-201.png"
as the result.
Online demo.