using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace ZeroLevel.HNSW { public enum Mode { None, ActiveCheck, InactiveCheck, ActiveInactiveCheck } public sealed class SearchContext { /// /// Список номеров которые разрешены к добавлению итогового результата, если поиск ведется в ограниченном наборе точек (например, после предварительной фильтрации) /// private HashSet _activeNodes; /// /// Список точек с которых начинается поиск в графе для расширения /// private HashSet _entryNodes; /// /// Режим работы алгоритма расширения, зависящий от того заданы ли ограничения в точках, и заданы ли точки начала поиска /// private Mode _mode; public Mode NodeCheckMode => _mode; public double PercentInTotal { get; private set; } = 0; public long AvaliableNodesCount => _activeNodes?.Count ?? 0; public SearchContext() { _mode = Mode.None; } /// /// Расчет процентного содержания точек доступных для использования в данном контексте, по отношению к общему количеству точек /// public SearchContext CaclulatePercentage(long total) { if ((_mode == Mode.ActiveCheck || _mode == Mode.ActiveInactiveCheck) && total > 0) { PercentInTotal = ((_activeNodes?.Count ?? 0 * 100d) / (double)total) / 100.0d; } return this; } public SearchContext SetPercentage(double percent) { PercentInTotal = percent; return this; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool _isActiveNode(int nodeId) => _activeNodes?.Contains(nodeId) ?? false; [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool _isEntryNode(int nodeId) => _entryNodes?.Contains(nodeId) ?? false; /// /// Проверка, подходит ли указанная точка для включения в набор расширения /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool IsActiveNode(int nodeId) { switch (_mode) { // Если задан набор разрешенных к использованию точек, проверяется вхождение в него case Mode.ActiveCheck: return _isActiveNode(nodeId); // Если задан набор точек начала поиска, проверка невхождения точки в него case Mode.InactiveCheck: return _isEntryNode(nodeId) == false; // Если задан и ограничивающий и начальный наборы точек, проверка и на ограничение и на невхождение в начальный набор case Mode.ActiveInactiveCheck: return false == _isEntryNode(nodeId) && _isActiveNode(nodeId); } return nodeId >= 0; } public IEnumerable EntryPoints => _entryNodes; public SearchContext SetActiveNodes(IEnumerable activeNodes) { if (activeNodes != null && activeNodes.Any()) { if (_mode == Mode.ActiveCheck || _mode == Mode.ActiveInactiveCheck) { throw new InvalidOperationException("Active nodes are already defined"); } _activeNodes = new HashSet(activeNodes); if (_mode == Mode.None) { _mode = Mode.ActiveCheck; } else if (_mode == Mode.InactiveCheck) { _mode = Mode.ActiveInactiveCheck; } } return this; } public SearchContext SetEntryPointsNodes(IEnumerable entryNodes) { if (entryNodes != null && entryNodes.Any()) { if (_mode == Mode.InactiveCheck || _mode == Mode.ActiveInactiveCheck) { throw new InvalidOperationException("Inctive nodes are already defined"); } _entryNodes = new HashSet(entryNodes); if (_mode == Mode.None) { _mode = Mode.InactiveCheck; } else if (_mode == Mode.ActiveCheck) { _mode = Mode.ActiveInactiveCheck; } } return this; } } }