fuzzy_duplicate_detector 1.0.0 copy "fuzzy_duplicate_detector: ^1.0.0" to clipboard
fuzzy_duplicate_detector: ^1.0.0 copied to clipboard

A lightweight Dart/Flutter package for detecting fuzzy duplicates in Arabic and English text using phonetic matching, edit distance, and similarity scoring, with support for diacritics removal, transl [...]

fuzzy_duplicate_detector ๐Ÿ” #

pub.flutter-io.cn Dart SDK License: MIT codecov

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:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Add tests for new functionality
  4. Run dart test and ensure all tests pass
  5. Run dart analyze and fix any warnings
  6. Submit a PR against main

License #

MIT ยฉ 2026 fuzzy_duplicate_detector contributors. See LICENSE for details.

0
likes
140
points
23
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A lightweight Dart/Flutter package for detecting fuzzy duplicates in Arabic and English text using phonetic matching, edit distance, and similarity scoring, with support for diacritics removal, transliteration, and word-order-independent comparison.

Repository (GitHub)
View/report issues

Topics

#text #nlp #fuzzy-matching #deduplication #arabic

License

MIT (license)

Dependencies

characters, collection

More

Packages that depend on fuzzy_duplicate_detector