fuzzy_duplicate_detector ๐
An ultra-pro Dart/Flutter package for detecting fuzzy duplicate records โ even when names are spelled differently, carry diacritics, mix Arabic and Latin scripts, or have words in different order.
The Problem
Every real-world Arabic-language database eventually looks like this:
| Record in DB | Same person? |
|---|---|
ู
ุญู
ุฏ ุนูู |
โ Yes |
ู
ุญู
ุฏ ุนููู |
โ Yes (diacritics) |
M.ALI |
โ Yes (transliterated + punctuation) |
ุนูู ู
ุญู
ุฏ |
โ Yes (word-order swap) |
ุฃุญู
ุฏ |
โ No |
fuzzy_duplicate_detector solves this with a multi-layer algorithm stack that
handles all of these cases simultaneously.
Features
| Feature | Description |
|---|---|
| ๐ค Arabic Soundex | Phonetic encoding designed for Arabic consonants |
| ๐ค English Soundex | Russell & Odell Soundex for Latin names |
| โ๏ธ Levenshtein Distance | Unicode-safe edit-distance similarity |
| ๐ Jaro-Winkler | Prefix-weighted similarity (great for names) |
| ๐ Token-Set Ratio | Word-order-independent matching |
| ๐ Transliteration | Arabic โ Latin cross-script phonetic matching |
| ๐งน Arabic Normalizer | Diacritics, Alef variants, Teh Marbuta, Tatweel |
| ๐งน Latin Normalizer | Accents, punctuation, case folding |
| ๐ Union-Find Clustering | Transitive grouping (AโB, BโC โ {A,B,C} one group) |
| โก Soundex-Blocked Mode | O(nยทlog n) for datasets > 500 items |
| โ๏ธ Fully Configurable | Threshold, weights, preprocessing toggles |
| ๐๏ธ 3 Built-in Presets | strict, lenient, crossScript |
Installation
dependencies:
fuzzy_duplicate_detector: ^1.0.0
dart pub get
Quick Start
import 'package:fuzzy_duplicate_detector/fuzzy_duplicate_detector.dart';
void main() {
final groups = FuzzyDedup.find([
'ู
ุญู
ุฏ ุนูู',
'ู
ุญู
ุฏ ุนููู', // diacritics
'M.ALI', // transliterated
'ุนูู ู
ุญู
ุฏ', // word order swapped
'ุฃุญู
ุฏ', // different person
]);
for (final g in groups) {
print('Canonical: ${g.canonical}');
print('Members : ${g.members}');
print('Confidence: ${g.confidencePercent}');
}
// Canonical: ู
ุญู
ุฏ ุนูู
// Members : [ู
ุญู
ุฏ ุนูู, ู
ุญู
ุฏ ุนููู, M.ALI, ุนูู ู
ุญู
ุฏ]
// Confidence: 91.4%
}
API Reference
FuzzyDedup โ Static Methods
FuzzyDedup.find(items, {config})
Find all duplicate groups in a list of strings.
final groups = FuzzyDedup.find(names);
// Returns: List<DuplicateGroup>
// Groups sorted by averageConfidence descending.
// Items with no duplicates are NOT included.
FuzzyDedup.compare(a, b, {config})
Compare a single pair and return full details.
final r = FuzzyDedup.compare('ู
ุตุทูู', 'ู
ุตุทูู');
print(r.confidencePercent); // e.g. "97.3%"
print(r.levenshteinScore); // 0.875
print(r.soundexMatch); // true
print(r.toDetailedString()); // full multi-line breakdown
FuzzyDedup.deduplicate(items, {config})
Return a list with duplicates replaced by their canonical form.
final clean = FuzzyDedup.deduplicate([
'ู
ุญู
ุฏ ุนูู', 'ู
ุญู
ุฏ ุนููู', 'ุฎุงูุฏ',
]);
// โ ['ู
ุญู
ุฏ ุนูู', 'ุฎุงูุฏ']
FuzzyDedup.findAsMap(items, {config})
Return Map<canonical, List<non-canonical duplicates>>.
final map = FuzzyDedup.findAsMap(names);
// { 'ู
ุญู
ุฏ ุนูู': ['ู
ุญู
ุฏ ุนููู', 'M.ALI'] }
FuzzyDedup.findUniqueItems(items, {config})
Return only items that are NOT in any duplicate group.
FuzzyDedup.groupSummary(items, {config})
Return a human-readable text report of all groups.
print(FuzzyDedup.groupSummary(names));
// Found 2 duplicate group(s) in 6 item(s):
//
// Group 1 [avg: 93.1%]
// Canonical : "ู
ุญู
ุฏ ุนูู"
// Duplicate : "ู
ุญู
ุฏ ุนููู" (96.8%)
// Duplicate : "M.ALI" (89.4%)
Configuration
FuzzyDedup.find(
names,
config: DedupConfig(
threshold: 0.85, // 85% minimum confidence
removeArabicDiacritics: true, // ู
ุญู
ุฏ ุนููู == ู
ุญู
ุฏ ุนูู
normalizeArabicAlef: true, // ุฃุญู
ุฏ == ุงุญู
ุฏ
normalizeArabicTehMarbuta: true, // ูุงุทู
ุฉ == ูุงุทู
ู
normalizeArabicAlefMaqsura: true,// ู
ุตุทูู == ู
ุตุทูู
enableTransliteration: true, // ู
ุญู
ุฏ โ Mohamed
clusteringThreshold: 500, // switch to blocked mode at 500 items
weights: AlgorithmWeights(
levenshtein: 0.35,
jaroWinkler: 0.30,
soundex: 0.20,
tokenSet: 0.15,
),
),
);
Built-in Presets
| Preset | Threshold | Use Case |
|---|---|---|
DedupConfig() |
0.80 | General purpose (default) |
DedupConfig.strict |
0.92 | Banking KYC, regulatory compliance |
DedupConfig.lenient |
0.65 | Exploratory analysis, human review |
DedupConfig.crossScript |
0.72 | Arabic โ Latin matching |
Algorithm Weight Presets
| Preset | Best For |
|---|---|
AlgorithmWeights() |
General Arabic/English names |
AlgorithmWeights.phoneticHeavy |
Noisy, OCR-scanned data |
AlgorithmWeights.editDistanceHeavy |
Clean, typed data |
Result Objects
MatchResult
final r = FuzzyDedup.compare('Abdullah', 'ุนุจุฏุงููู');
r.a // 'Abdullah'
r.b // 'ุนุจุฏุงููู'
r.confidence // 0.813
r.confidencePercent // '81.3%'
r.levenshteinScore // 0.0 (different scripts)
r.jaroWinklerScore // 0.0 (different scripts)
r.soundexA // 'A430'
r.soundexB // 'ุน590'
r.soundexMatch // false
r.soundexScore // 0.0
r.tokenSetScore // 0.0
r.transliterationBonus // 0.122 โ cross-script bonus!
r.isDuplicate // true (with threshold 0.60)
DuplicateGroup
final g = groups.first;
g.canonical // 'ู
ุญู
ุฏ ุนูู' (longest member)
g.members // ['ู
ุญู
ุฏ ุนูู', 'ู
ุญู
ุฏ ุนููู', 'M.ALI']
g.size // 3
g.averageConfidence // 0.914
g.confidencePercent // '91.4%'
g.maxConfidence // 0.968
g.minConfidence // 0.861
g.nonCanonicalMembers // ['ู
ุญู
ุฏ ุนููู', 'M.ALI']
g.pairwiseResults // List<MatchResult>
Algorithm Stack โ How It Works
Input: ["ู
ุญู
ุฏ ุนูู", "ู
ุญู
ุฏ ุนููู", "M.ALI", "ุฃุญู
ุฏ"]
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GroupBuilder โ โ orchestrates the pipeline
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ For each pair (i, j):
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ScoreCombiner.compare(a, b) โ
โ โ
โ 1. Normalize โ ArabicNormalizer โ
โ LatinNormalizer โ
โ โ
โ 2. Levenshtein.similarity() โ levScore โ
โ โ
โ 3. JaroWinkler.similarity() โ jwScore โ
โ โ
โ 4. ArabicSoundex.encode() โ codeA, codeB โ
โ EnglishSoundex.encode() (auto-detect) โ
โ soundexScore = 1.0 / 0.75 / 0.5 / 0.0 โ
โ โ
โ 5. Transliterator.arabicToLatin() โ
โ (cross-script bonus, max +0.15) โ
โ โ
โ 6. Token-Set Ratio โ tokenScore โ
โ (sort tokens, then JW) โ
โ โ
โ 7. confidence = ฮฃ(wแตข ร scoreแตข) + bonus โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ if confidence โฅ threshold
โผ
UnionFind.union(i, j)
โ
โผ
UnionFind.getGroups()
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Output: List<DuplicateGroup> โ
โ sorted by averageConfidence desc โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Performance
| Dataset Size | Mode | Typical Time (M1 Mac) |
|---|---|---|
| 50 names | Full O(nยฒ) | ~2 ms |
| 200 names | Full O(nยฒ) | ~30 ms |
| 500 names | Full O(nยฒ) | ~180 ms |
| 1000 names | Soundex-blocked | ~40 ms |
| 5000 names | Soundex-blocked | ~180 ms |
Run the bundled benchmark:
dart run benchmark/large_dataset_benchmark.dart
Running Tests
dart test
The test suite covers:
- All 7 FuzzyDedup static methods
- All Arabic normalisation steps
- Latin normalisation (accents, punctuation, รโss)
- Transliterator script detection
- Levenshtein distance (classical + Arabic)
- English Soundex (Robert/Rupert, Mohamed/Muhammad)
- Arabic Soundex (ู ุตุทูู/ู ุตุทูู, diacritics invariance)
- Jaro-Winkler (MARTHA/MARHTA, prefix bonus)
- AlgorithmWeights sum validation
- UnionFind (transitive grouping, path compression)
- DedupConfig (presets, copyWith)
Use Cases
- ๐ฆ Banking KYC โ detect duplicate customer onboarding applications
- ๐ข CRM systems โ merge duplicate contact records
- ๐๏ธ Government databases โ national ID / voter registry deduplication
- ๐ E-commerce โ merge duplicate customer accounts
- ๐ Data warehouses โ ETL pipeline deduplication
- ๐ฅ Healthcare โ patient record deduplication (HIPAA contexts)
- ๐ Libraries โ author name deduplication in bibliographic data
Dependencies
| Package | Version | Purpose |
|---|---|---|
characters |
^1.3.0 | Unicode grapheme-cluster iteration |
collection |
^1.18.0 | groupBy, ListEquality |
Both are official Dart team packages โ minimal footprint, no transitive deps.
Contributing
Pull requests are welcome! Please:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Add tests for new functionality
- Run
dart testand ensure all tests pass - Run
dart analyzeand fix any warnings - Submit a PR against
main
License
MIT ยฉ 2026 fuzzy_duplicate_detector contributors. See LICENSE for details.
Libraries
- fuzzy_duplicate_detector
- fuzzy_duplicate_detector