https://github.com/vishwa-karthik/fuzzy_bolt
A powerful and optimized fuzzy search algorithm with typo tolerance and ranking in Dart
https://github.com/vishwa-karthik/fuzzy_bolt
dart dart-package flutter fuzzy-search ranking-algorithm search search-algorithm suggestions
Last synced: about 2 months ago
JSON representation
A powerful and optimized fuzzy search algorithm with typo tolerance and ranking in Dart
- Host: GitHub
- URL: https://github.com/vishwa-karthik/fuzzy_bolt
- Owner: Vishwa-Karthik
- License: bsd-3-clause
- Created: 2025-03-07T17:48:00.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-30T18:00:51.000Z (about 1 year ago)
- Last Synced: 2025-05-16T09:52:56.156Z (about 1 year ago)
- Topics: dart, dart-package, flutter, fuzzy-search, ranking-algorithm, search, search-algorithm, suggestions
- Language: Dart
- Homepage: https://pub.dev/packages/fuzzy_bolt
- Size: 56.6 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Fuzzy Bolt
**An advanced Fuzzy Search Algorithm with intelligent typo correction, adaptive ranking, porter stemming and lightning-fast performance.**
[](https://pub.dev/packages/fuzzy_bolt)
[](LICENSE)
## 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](https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance) for ranking the results.
+ Uses [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) to handle the typo errors in the query if any.
+ Uses [Porter Stemming](https://cloud.google.com/discover/what-is-stemming) process to intelligently search against the dataset.
+ Leverage host's [Isolate](https://dart.dev/language/isolates) 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:
```yaml
dependencies:
fuzzy_bolt:
```
Then grab it:
```bash
dart pub get # or flutter pub get
```
---
## π Quick Start
Here's the simplest way to get started:
```dart
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(
users,
'Alise', // Yep, typo tolerance built-in!
selectors: [(u) => u.name],
);
// Want to see how confident each match is?
final scored = await FuzzyBolt.searchWithScores(
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.
```dart
Future> FuzzyBolt.search(
List dataset,
String query,
{
required List 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:**
```dart
// Simple name search
final results = await FuzzyBolt.search(
users,
'alice',
selectors: [(u) => u.name],
);
// Search across multiple fields (name, email, department, etc.)
final results = await FuzzyBolt.search(
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(
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.
```dart
Future>> FuzzyBolt.searchWithScores(
List dataset,
String query,
{
required List selectors,
double strictThreshold = 0.85,
double typeThreshold = 0.65,
int? maxResults,
}
)
```
**What you get back:**
```dart
class FuzzyResult {
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:**
```dart
final results = await FuzzyBolt.searchWithScores(
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.
```dart
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(
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.
```dart
final results = await FuzzyBolt.searchWithTextProcessing(
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.
```dart
// 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.
```dart
// Pick a cleaning strategy
final cleaner = StandardDatasetCleaner.minimal(); // Gentle cleaning
final cleaner = StandardDatasetCleaner.forEcommerce(); // Balanced (recommended)
final cleaner = StandardDatasetCleaner.aggressive(); // 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(
result.cleanedItems, // Use the cleaned list
'running shoes',
selectors: [(p) => p.name],
);
```
---
### 7. Cache Management
```dart
// 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**
```dart
final results = await FuzzyBolt.searchWithScores(
products,
'runing shos', // User can't spell? We got you.
selectors: [(p) => p.name, (p) => p.description],
);
```
**HR/Directory: Find employees across departments**
```dart
final results = await FuzzyBolt.search(
employees,
'john engineering',
selectors: [(e) => e.name, (e) => e.department, (e) => e.title],
);
```
**Content/Docs: Smart document search**
```dart
final results = await FuzzyBolt.searchWithTextProcessing(
docs,
'professional runners training',
selectors: [(d) => d.title, (d) => d.content],
enableStemming: true,
removeStopWords: true,
);
```
**Big Data: Search through 10,000+ items**
```dart
final results = await FuzzyBolt.search(
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
```dart
maxResults: 10 // Stop after finding 10 matches
```
**Pro tip #2:** Lower the isolate threshold for better performance on medium datasets
```dart
isolateThreshold: 500 // Use parallel processing at 500+ items (default: 1000)
```
**Pro tip #3:** Turn off features you don't need
```dart
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
```dart
// 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(
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`:
```dart
import 'package:flutter/foundation.dart' show kIsWeb;
final results = await FuzzyBolt.search(
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
```dart
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
```dart
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:
```bash
dart test
```
---
## π License
BSD-3-Clause License β Free to use, even commercially. See [LICENSE](LICENSE) for details.
---
## π€ Need Help?
- π Found a bug? [Report it here](https://github.com/Vishwa-Karthik/fuzzy_bolt/issues)
- π¬ Have questions? [Start a discussion](https://github.com/Vishwa-Karthik/fuzzy_bolt/discussions)
- β Like it? Give us a star on GitHub!
---
**Built with β€οΈ for Dart Community**