To rank a list you first need a result you can compare - a candidate paired with its score and matched positions. Today you score every matching candidate into a Result.
Produce a Result (candidate, score, positions) for each candidate that matches the query, skipping those that do not.
Ranking needs a unit of comparison. That unit is a Result: a candidate bundled with the score of its best alignment and the positions behind that score. Producing one is just composing what chapter two built - gate each candidate with the match test, and for the ones that pass, run the dynamic program to get the score and trace back the positions.
Today rank returns the results in input order, unsorted - the sorting is the next lesson’s single idea. Non-matches are simply dropped, so the result list is already the filtered set, now annotated with quality. Bundling score and positions together here means every later step - sorting, tie-breaking, top-K, rendering - works on one tidy value instead of recomputing scores. This is the shape the whole back half of the project passes around.
type Result struct {Candidate stringScore intPositions []int}// For each candidate: run the match gate, and if it passes, compute// the best score and its positions (the DP + traceback from ch. 2).func rank(query string, candidates []string) []Result {var out []Result// append a Result per matching candidate, input order for nowreturn out}