// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package scheduler // LimitIterator is a RankIterator used to limit the number of options // that are returned before we artificially end the stream. type LimitIterator struct { ctx Context source RankIterator limit int maxSkip int scoreThreshold float64 seen int skippedNodes []*RankedNode skippedNodeIndex int } // NewLimitIterator returns a LimitIterator with a fixed limit of returned options. // Up to maxSkip options whose score is below scoreThreshold are skipped // if there are additional options available in the source iterator func NewLimitIterator(ctx Context, source RankIterator, limit int, scoreThreshold float64, maxSkip int) *LimitIterator { iter := &LimitIterator{ ctx: ctx, source: source, limit: limit, maxSkip: maxSkip, scoreThreshold: scoreThreshold, skippedNodes: make([]*RankedNode, 0, maxSkip), } return iter } func (iter *LimitIterator) SetLimit(limit int) { iter.limit = limit } func (iter *LimitIterator) Next() *RankedNode { if iter.seen == iter.limit { return nil } option := iter.nextOption() if option == nil { return nil } if len(iter.skippedNodes) < iter.maxSkip { // Try skipping ahead up to maxSkip to find an option with score lesser than the threshold for option != nil && option.FinalScore <= iter.scoreThreshold && len(iter.skippedNodes) < iter.maxSkip { iter.skippedNodes = append(iter.skippedNodes, option) option = iter.source.Next() } } iter.seen += 1 if option == nil { // Didn't find anything, so use the skipped nodes instead return iter.nextOption() } return option } // nextOption uses the iterator's list of skipped nodes if the source iterator is exhausted func (iter *LimitIterator) nextOption() *RankedNode { sourceOption := iter.source.Next() if sourceOption == nil && iter.skippedNodeIndex < len(iter.skippedNodes) { skippedOption := iter.skippedNodes[iter.skippedNodeIndex] iter.skippedNodeIndex += 1 return skippedOption } return sourceOption } func (iter *LimitIterator) Reset() { iter.source.Reset() iter.seen = 0 iter.skippedNodes = make([]*RankedNode, 0, iter.maxSkip) iter.skippedNodeIndex = 0 } // MaxScoreIterator is a RankIterator used to return only a single result // of the item with the highest score. This iterator will consume all of the // possible inputs and only returns the highest ranking result. type MaxScoreIterator struct { ctx Context source RankIterator max *RankedNode } // NewMaxScoreIterator returns a MaxScoreIterator over the given source func NewMaxScoreIterator(ctx Context, source RankIterator) *MaxScoreIterator { iter := &MaxScoreIterator{ ctx: ctx, source: source, } return iter } func (iter *MaxScoreIterator) Next() *RankedNode { // Check if we've found the max, return nil if iter.max != nil { return nil } // Consume and determine the max for { option := iter.source.Next() if option == nil { return iter.max } if iter.max == nil || option.FinalScore > iter.max.FinalScore { iter.max = option } } } func (iter *MaxScoreIterator) Reset() { iter.source.Reset() iter.max = nil }