using System;
using Utilities.Exceptions;
namespace Algorithms.Search
{
/// <summary>
/// The idea: you could combine the advantages from both binary-search and interpolation search algorithm.
/// Time complexity:
/// worst case: Item couldn't be found: O(log n),
/// average case: O(log log n),
/// best case: O(1).
/// Note: This algorithm is recursive and the array has to be sorted beforehand.
/// </summary>
public class FastSearcher
{
/// <summary>
/// Finds index of first item in array that satisfies specified term
/// throws ItemNotFoundException if the item couldn't be found.
/// </summary>
/// <param name="array">Span of sorted numbers which will be used to find the item.</param>
/// <param name="item">Term to check against.</param>
/// <returns>Index of first item that satisfies term.</returns>
/// <exception cref="ItemNotFoundException"> Gets thrown when the given item couldn't be found in the array.</exception>
public int FindIndex(Span<int> array, int item)
{
if (array.Length == 0)
{
throw new ItemNotFoundException();
}
if (item < array[0] || item > array[^1])
{
throw new ItemNotFoundException();
}
if (array[0] == array[^1])
{
return item == array[0] ? 0 : throw new ItemNotFoundException();
}
var (left, right) = ComputeIndices(array, item);
var (from, to) = SelectSegment(array, left, right, item);
return from + FindIndex(array.Slice(from, to - from + 1), item);
}
private (int left, int right) ComputeIndices(Span<int> array, int item)
{
var indexBinary = array.Length / 2;
int[] section =
{
array.Length - 1,
item - array[0],
array[^1] - array[0],
};
var indexInterpolation = section[0] * section[1] / section[2];
// Left is min and right is max of the indices
return indexInterpolation > indexBinary
? (indexBinary, indexInterpolation)
: (indexInterpolation, indexBinary);
}
private (int from, int to) SelectSegment(Span<int> array, int left, int right, int item)
{
if (item < array[left])
{
return (0, left - 1);
}
if (item < array[right])
{
return (left, right - 1);
}
return (right, array.Length - 1);
}
}
}