extends RefCounted class_name FuzzySearch ## Simple fuzzy matching algorithm for text search ## Returns a score between 0 (no match) and 1 (perfect match) static func match(query: String, target: String) -> float: if query.is_empty(): return 1.0 query = query.to_lower() target = target.to_lower() # Exact match if target == query: return 1.0 # Contains query as substring if target.find(query) != -1: return 0.9 # Fuzzy match - all characters in query must appear in order in target var query_idx: int = 0 var target_idx: int = 0 var matches: int = 0 var consecutive_bonus: float = 0.0 var last_match_idx: int = -1 while query_idx < query.length() and target_idx < target.length(): if query[query_idx] == target[target_idx]: matches += 1 if last_match_idx != -1 and target_idx == last_match_idx + 1: consecutive_bonus += 0.1 last_match_idx = target_idx query_idx += 1 target_idx += 1 # All characters matched if query_idx == query.length(): var base_score: float = float(matches) / float(query.length()) var bonus: float = min(consecutive_bonus, 0.3) # Cap bonus at 0.3 return base_score * 0.7 + bonus return 0.0 ## Sort an array of items by fuzzy match score ## items should be Dictionary with at least a "name" or specified key field static func sort_by_match(query: String, items: Array, key: String = "name") -> Array: if query.is_empty(): return items.duplicate() var scored: Array[Dictionary] = [] for item in items: var item_name: String = item.get(key, "") var score: float = match(query, item_name) if score > 0: scored.append({"item": item, "score": score}) # Sort by score descending scored.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a["score"] > b["score"] ) return scored.map(func(s): return s["item"])