fuzzy_bolt 2.0.1 copy "fuzzy_bolt: ^2.0.1" to clipboard
fuzzy_bolt: ^2.0.1 copied to clipboard

A high-performance fuzzy search algorithm in Dart, designed for intelligent auto-suggestions, typo tolerance, and fast string matching.

Fuzzy Bolt #

An advanced Fuzzy Search Algorithm with intelligent typo correction, adaptive ranking, porter stemming and lightning-fast performance.

pub package License: BSD-3-Clause

Why Fuzzy Bolt ?? #

I’ve explored several fuzzy search packages, but haven’t found one that intelligently corrects typos in queries and performs stemming on the dataset while searching

  • Uses Jaro–Winkler Distance for ranking the results.
  • Uses Levenshtein Distance to handle the typo errors in the query if any.
  • Uses Porter Stemming process to intelligently search against the dataset.
  • Leverage host's Isolate mechanism if the dataset becomes huge.
  • Allow developers to switch to non-isolate mechanim for Web platform - Since Isolate do not work on them.
  • Allow developers to set their threshold on results for better accuracy.

Use Case Applications #

  • Local Database Search: Perfect for running fuzzy queries directly on local datasets like SQLite, Hive, or Isar.

  • Post-API Result Search: Enhance your UX by adding an extra layer of fuzzy search after fetching data from remote APIs.

  • In-Memory State Search: Great for filtering and ranking results from app state (e.g in-memory lists, BLoC/Cubit states, Provider data, etc.).

  • Search Bars & Autocomplete Fields: Supercharge your TextField or SearchDelegate with typo-tolerant and intent-aware results.

  • Offline-First Applications: Helpful in apps that prioritize offline functionality and require local, fast search.

  • Data Cleaning & Record Linking: Use it for fuzzy matching and deduplication tasks (e.g., merging similar records in datasets).

  • Command Palette / Quick Actions Search: Perfect for developer tools or admin dashboards where users trigger commands via text input.

📦 Installation #

Add Fuzzy Bolt to your project:

dependencies:
  fuzzy_bolt: ^2.0.0

Then grab it:

dart pub get  # or flutter pub get

🚀 Quick Start #

Here's the simplest way to get started:

import 'package:fuzzy_bolt/fuzzy_bolt.dart';

final users = [
  User(name: 'Alice Johnson', email: 'alice@example.com'),
  User(name: 'Bob Smith', email: 'bob@example.com'),
];

// Basic search - just give me the matching items
final results = await FuzzyBolt.search<User>(
  users,
  'Alise',  // Yep, typo tolerance built-in!
  selectors: [(u) => u.name],
);

// Want to see how confident each match is?
final scored = await FuzzyBolt.searchWithScores<User>(
  users,
  'alice',
  selectors: [(u) => u.name],
);

for (final result in scored) {
  print('${result.item.name}: ${(result.score * 100).toInt()}% match');
  // Output: Alice Johnson: 100% match
}

📖 Complete API Guide #

1. Basic Search → FuzzyBolt.search() #

When to use: You just want the matching items, no extra details needed.

Future<List<T>> FuzzyBolt.search<T>(
  List<T> dataset,
  String query,
  {
    required List<String Function(T)> selectors,  // Tell it which fields to search
    double strictThreshold = 0.85,     // How strict for exact matches (0.0-1.0)
    double typeThreshold = 0.65,       // How forgiving for typos (0.0-1.0)
    int? maxResults,                   // Cap the results
    bool skipIsolate = false,          // Set true for web
  }
)

Real examples:

// Simple name search
final results = await FuzzyBolt.search<User>(
  users,
  'alice',
  selectors: [(u) => u.name],
);

// Search across multiple fields (name, email, department, etc.)
final results = await FuzzyBolt.search<Product>(
  products,
  'running shoes',
  selectors: [(p) => p.name, (p) => p.description, (p) => p.category],
);

// Just give me the top 5 results
final top5 = await FuzzyBolt.search<User>(
  users,
  'john',
  selectors: [(u) => u.name],
  maxResults: 5,
);

2. Search With Scores → FuzzyBolt.searchWithScores() #

When to use: You need to show users how confident each match is, or rank results.

Future<List<FuzzyResult<T>>> FuzzyBolt.searchWithScores<T>(
  List<T> dataset,
  String query,
  {
    required List<String Function(T)> selectors,
    double strictThreshold = 0.85,
    double typeThreshold = 0.65,
    int? maxResults,
  }
)

What you get back:

class FuzzyResult<T> {
  final T item;              // Your original item
  final double score;        // How well it matched: 0.0 (terrible) to 1.0 (perfect)
  final String matchedText;  // The actual text that matched
}

Example:

final results = await FuzzyBolt.searchWithScores<Product>(
  products,
  'runing shos',  // Multiple typos? No problem!
  selectors: [(p) => p.name],
);

for (final result in results) {
  final confidence = (result.score * 100).toStringAsFixed(1);
  print('${result.item.name} - $confidence% match');
}
// Output:
// Running Shoes Pro - 87.5% match
// Walking Shoes - 65.2% match

3. Search With Config → FuzzyBolt.searchWithConfig() #

When to use: You need fine-tuned control over how the search behaves.

final strictConfig = FuzzySearchConfig(
  strictThreshold: 0.95,  // Very picky about matches
  typeThreshold: 0.85,    // Don't be too forgiving with typos
  maxResults: 10,
);

final results = await FuzzyBolt.searchWithConfig<Product>(
  products,
  'iPhone 15',
  [(p) => p.name],
  strictConfig,
);

4. Text Processing Search → FuzzyBolt.searchWithTextProcessing() #

When to use: You need advanced text processing like stemming and stop word removal.

final results = await FuzzyBolt.searchWithTextProcessing<Article>(
  articles,
  'the runners are running',
  selectors: [(a) => a.title, (a) => a.content],
  enableStemming: true,      // "runners" becomes "runner", "running" becomes "run"
  removeStopWords: true,     // Removes "the", "are", etc.
);

5. Porter Stemmer → Direct Word Manipulation #

When to use: You need to stem words yourself for preprocessing or analysis.

// Stem individual words
print(PorterStemmer.stem('running'));  // "run"
print(PorterStemmer.stem('flies'));    // "fli"
print(PorterStemmer.stem('dogs'));     // "dog"

// Process entire sentences
final text = 'The runners were running quickly';
print(PorterStemmer.stemText(text));   // "runner were run quickli"

// Check the stop words list (77 common English words)
print(PorterStemmer.stopWords.contains('the'));  // true

6. Dataset Cleaning → StandardDatasetCleaner #

When to use: Your data has duplicates or low-quality entries you want to filter out before searching.

// Pick a cleaning strategy
final cleaner = StandardDatasetCleaner.minimal<Product>();        // Gentle cleaning
final cleaner = StandardDatasetCleaner.forEcommerce<Product>();   // Balanced (recommended)
final cleaner = StandardDatasetCleaner.aggressive<Product>();     // Maximum cleaning

// Clean your dataset
final result = cleaner.cleanDataset(
  products,
  [(p) => p.name],
  qualityThreshold: 0.3,
  removeDuplicates: true,
);

print('Original: ${result.statistics.originalCount}');
print('Cleaned: ${result.statistics.cleanedCount}');
print('Removed: ${result.statistics.duplicatesRemoved} duplicates');

// Now search the cleaned data
final searchResults = await FuzzyBolt.search<Product>(
  result.cleanedItems,  // Use the cleaned list
  'running shoes',
  selectors: [(p) => p.name],
);

7. Cache Management #

// Clear the internal text processing cache
FuzzyBolt.clearTextCache();

// Check cache stats
final stats = FuzzyBolt.getTextCacheStats();
print('Cache has ${stats["cacheSize"]} items (max: ${stats["maxCacheSize"]})');

💡 Real-World Use Cases #

E-commerce: Product search with typos

final results = await FuzzyBolt.searchWithScores<Product>(
  products,
  'runing shos',  // User can't spell? We got you.
  selectors: [(p) => p.name, (p) => p.description],
);

HR/Directory: Find employees across departments

final results = await FuzzyBolt.search<Employee>(
  employees,
  'john engineering',
  selectors: [(e) => e.name, (e) => e.department, (e) => e.title],
);

Content/Docs: Smart document search

final results = await FuzzyBolt.searchWithTextProcessing<Document>(
  docs,
  'professional runners training',
  selectors: [(d) => d.title, (d) => d.content],
  enableStemming: true,
  removeStopWords: true,
);

Big Data: Search through 10,000+ items

final results = await FuzzyBolt.search<User>(
  massiveUserList,  // 10,000+ users
  'alice',
  selectors: [(u) => u.name],
  maxResults: 10,  // Get top 10 only
);
// Automatically uses parallel processing—no config needed!

⚡ Performance Tips #

Pro tip #1: Limit your results if you don't need everything

maxResults: 10  // Stop after finding 10 matches

Pro tip #2: Lower the isolate threshold for better performance on medium datasets

isolateThreshold: 500  // Use parallel processing at 500+ items (default: 1000)

Pro tip #3: Turn off features you don't need

enableStemming: false    // Skip if you don't need word variation matching
enableCleaning: false    // Skip if your data is already clean

Pro tip #4: Clean once, search many times

// Clean your dataset once
final cleaned = cleaner.cleanDataset(products, [(p) => p.name]);

// Then reuse the cleaned data for multiple searches
for (final query in userQueries) {
  final results = await FuzzyBolt.search<Product>(
    cleaned.cleanedItems,  // Already clean!
    query,
    selectors: [(p) => p.name],
    enableCleaning: false,  // No need to clean again
  );
}

What to expect:

Dataset Size Parallel Processing? Typical Time
Under 100 items Nope < 10ms
100-1,000 items Nope 10-100ms
1,000-10,000 items Yes (automatic) 100ms-1s
10,000+ items Yes (automatic) 1-3s

🌐 Web Platform Note #

Heads up: Isolates aren't available on web. Just set skipIsolate: true:

import 'package:flutter/foundation.dart' show kIsWeb;

final results = await FuzzyBolt.search<User>(
  users,
  'query',
  selectors: [(u) => u.name],
  skipIsolate: kIsWeb,  // Handles web automatically
);

🎛️ Understanding Thresholds #

Think of thresholds like "how picky should the search be?"

strictThreshold (default: 0.85)

  • Higher (like 0.95) = Be very picky, only near-perfect matches
  • Lower (like 0.7) = Be more chill, allow more variation
strictThreshold: 0.95  // "Alice" matches "Alice" only
strictThreshold: 0.85  // "Alice" matches "Alice" and "Alicia"
strictThreshold: 0.7   // "Alice" matches "Alice", "Alicia", "Alise"

typeThreshold (default: 0.65)

  • Higher (like 0.85) = Strict typo checking
  • Lower (like 0.4) = Very forgiving with typos
typeThreshold: 0.85  // "Alise" might not match "Alice"
typeThreshold: 0.65  // "Alise" matches "Alice" ✓
typeThreshold: 0.4   // "Alce" matches "Alice" ✓

📱 Platform Support #

Works everywhere Dart works:

Platform Status Notes
Android Full support with isolates
iOS Full support with isolates
macOS Full support with isolates
Windows Full support with isolates
Linux Full support with isolates
Web Use skipIsolate: true

🧪 Testing & Quality #

87 comprehensive tests covering:

  • Basic search & typo tolerance
  • Multi-field search
  • Large datasets (10,000+ items)
  • Text processing & stemming
  • Dataset cleaning
  • Edge cases (unicode, empty strings, special characters)
  • Concurrent access & memory efficiency

Run tests yourself:

dart test

📄 License #

BSD-3-Clause License – Free to use, even commercially. See LICENSE for details.


🤝 Need Help? #


Built with ❤️ for Dart Community

6
likes
160
points
894
downloads

Publisher

verified publishervishwakarthik.in

Weekly Downloads

A high-performance fuzzy search algorithm in Dart, designed for intelligent auto-suggestions, typo tolerance, and fast string matching.

Repository (GitHub)
View/report issues

Topics

#fuzzy-search #search-ranking #text-processing #auto-suggestions #ranking

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

collection

More

Packages that depend on fuzzy_bolt