rust 108 lines · 3 tabs

Fuzzy Search Ranker with Subsequence Matching and Score-Based Sorting in Rust

Shared by codesnips Sep 2026
3 tabs
const CONSECUTIVE_BONUS: f64 = 8.0;
const WORD_START_BONUS: f64 = 10.0;
const MATCH_BASE: f64 = 4.0;
const LEADING_PENALTY: f64 = 0.5;

#[derive(Debug, Clone, Copy)]
pub struct Match {
    pub index: usize,
    pub score: f64,
}

fn is_boundary(prev: Option<char>) -> bool {
    match prev {
        None => true,
        Some(c) => c == ' ' || c == '_' || c == '-' || c == '/' || c == '.',
    }
}

pub fn fuzzy_score(candidate: &str, query: &str) -> Option<f64> {
    if query.is_empty() {
        return Some(0.0);
    }

    let q: Vec<char> = query.to_ascii_lowercase().chars().collect();
    let mut qi = 0usize;
    let mut score = 0.0f64;
    let mut prev_matched = false;
    let mut prev_char: Option<char> = None;
    let mut matched_yet = false;

    for ch in candidate.chars() {
        let lower = ch.to_ascii_lowercase();
        if qi < q.len() && lower == q[qi] {
            score += MATCH_BASE;
            if prev_matched {
                score += CONSECUTIVE_BONUS;
            }
            if is_boundary(prev_char) {
                score += WORD_START_BONUS;
            }
            qi += 1;
            prev_matched = true;
            matched_yet = true;
        } else {
            if !matched_yet {
                score -= LEADING_PENALTY;
            }
            prev_matched = false;
        }
        prev_char = Some(ch);
    }

    if qi == q.len() {
        Some(score)
    } else {
        None
    }
}
3 files · rust Explain with highlit

This snippet implements a small fuzzy search ranker of the kind used to power command palettes, file finders, and autocomplete boxes. The core idea is subsequence matching: a query matches a candidate if every character of the query appears in the candidate in order, not necessarily contiguously. Beyond a boolean match, the ranker computes a numeric score so results can be sorted by relevance, which is what makes fuzzy finders feel responsive.

In matcher.rs, fuzzy_score walks the candidate once, advancing a cursor through the query on each matching character. Matching is case-insensitive via to_ascii_lowercase, but the original casing is preserved for display. The scoring model rewards the qualities users intuitively expect: a CONSECUTIVE_BONUS for adjacent matches so contiguous runs beat scattered ones, a WORD_START_BONUS for characters that begin a word (start of string or after a separator, tracked by is_boundary), and a LEADING_PENALTY that gently discounts unmatched characters before the first hit so prefix matches float to the top. If the query is exhausted the function returns Some(score); if the candidate ends first it returns None, cleanly separating the match/no-match decision from ranking.

The Match struct in matcher.rs bundles the candidate index with its score, keeping the ranker independent of how candidates are stored. The empty-query case returns a neutral score so an unfiltered list still renders.

In ranker.rs, rank maps candidates through fuzzy_score, keeps only the Some results with filter_map, then sorts. The sort uses partial_cmp because scores are f64, and deliberately sorts by descending score first, breaking ties by ascending index so equally-scored items keep their original stable order. This tie-break matters: without it, results shuffle unpredictably between keystrokes, which feels broken to users. partial_cmp returns Option because floats can be NaN; falling back to Ordering::Equal avoids a panic even though this scoring never produces NaN.

The main.rs tab shows the pieces working together against a fixed candidate list, printing scored, sorted results for a sample query. The design keeps the hot path allocation-light and the scoring weights are plain constants, so tuning ranking behavior is a matter of adjusting a few numbers rather than restructuring the algorithm.


Related snips

erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs
rust
use clap::Parser;

#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
    #[arg(short, long)]

clap for CLI argument parsing with derive macros

rust cli clap
by Marcus Chen 1 tab
rust
use anyhow::{Context, Result};
use std::fs;

fn load_config(path: &str) -> Result<String> {
    fs::read_to_string(path)
        .with_context(|| format!("failed to read config from {}", path))

anyhow::Context for adding error context without custom types

rust error-handling cli
by Marcus Chen 1 tab
typescript
import { useEffect, useState } from "react";

export function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState<T>(value);

  useEffect(() => {

Debounced search input (React)

react hooks debounce
by codesnips 3 tabs
sql
-- Create table with tsvector column
CREATE TABLE articles (
  id SERIAL PRIMARY KEY,
  title VARCHAR(200),
  content TEXT,
  author VARCHAR(100),

Full-text search with PostgreSQL and tsvector

postgresql full-text-search tsvector
by Maria Garcia 2 tabs
rust
use std::process::Command;

fn main() -> std::io::Result<()> {
    let output = Command::new("ls")
        .arg("-la")
        .output()?;

std::process::Command for spawning external processes

rust processes cli
by Marcus Chen 1 tab

Share this code

Here's the card — post it anywhere.

Fuzzy Search Ranker with Subsequence Matching and Score-Based Sorting in Rust — share card
Link copied