An open API service indexing awesome lists of open source software.

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

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.**

[![pub package](https://img.shields.io/pub/v/fuzzy_bolt.svg)](https://pub.dev/packages/fuzzy_bolt)
[![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue)](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**