fuzzy_bolt 2.0.1
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.
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? #
- 🐛 Found a bug? Report it here
- 💬 Have questions? Start a discussion
- ⭐ Like it? Give us a star on GitHub!
Built with ❤️ for Dart Community