diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
new file mode 100644
index 0000000..f311817
--- /dev/null
+++ b/.github/workflows/pr-validation.yml
@@ -0,0 +1,134 @@
+name: 🔍 PR Title Validation
+
+on:
+ pull_request:
+ types: [opened, edited, synchronize]
+
+jobs:
+ validate-title:
+ name: 📋 Validate PR Title Format
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: 📥 Checkout Code
+ uses: actions/checkout@v4
+
+ - name: 🔍 Validate PR Title
+ env:
+ PR_TITLE: ${{ github.event.pull_request.title }}
+ run: |
+ echo "🔍 Validating PR title: '$PR_TITLE'"
+
+ # Pattern pour les conventions de commit
+ # Format: type(scope): description
+ if [[ "$PR_TITLE" =~ ^(feat|fix|docs|style|refactor|test|chore)(\([a-z0-9-]+\))?: .{1,50}$ ]]; then
+ echo "✅ PR title follows conventional format"
+ echo "## ✅ PR Title Validation" >> $GITHUB_STEP_SUMMARY
+ echo "Le titre de la PR suit les conventions du projet:" >> $GITHUB_STEP_SUMMARY
+ echo "\`$PR_TITLE\`" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "❌ PR title should follow format: type(scope): description"
+ echo "Current title: $PR_TITLE"
+ echo ""
+ echo "Examples:"
+ echo " feat(autocomplete): add new autocompletion engine"
+ echo " fix(similarity): correct Levenshtein calculation"
+ echo " docs(readme): update API documentation"
+ echo ""
+ echo "Valid types: feat, fix, docs, style, refactor, test, chore"
+
+ # Ajouter au résumé GitHub
+ echo "## ❌ PR Title Validation Failed" >> $GITHUB_STEP_SUMMARY
+ echo "Le titre de la PR ne suit pas les conventions:" >> $GITHUB_STEP_SUMMARY
+ echo "\`$PR_TITLE\`" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### 📋 Format attendu:" >> $GITHUB_STEP_SUMMARY
+ echo "\`type(scope): description\`" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### ✅ Exemples valides:" >> $GITHUB_STEP_SUMMARY
+ echo "- \`feat(autocomplete): add new autocompletion engine\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`fix(similarity): correct Levenshtein calculation\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`docs(readme): update API documentation\`" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### 🔧 Types valides:" >> $GITHUB_STEP_SUMMARY
+ echo "\`feat\`, \`fix\`, \`docs\`, \`style\`, \`refactor\`, \`test\`, \`chore\`" >> $GITHUB_STEP_SUMMARY
+
+ exit 1
+ fi
+
+ suggest-labels:
+ name: 🏷️ Suggest PR Labels
+ runs-on: ubuntu-latest
+ needs: validate-title
+ if: success()
+
+ steps:
+ - name: 🏷️ Analyze PR and Suggest Labels
+ env:
+ PR_TITLE: ${{ github.event.pull_request.title }}
+ PR_BODY: ${{ github.event.pull_request.body }}
+ run: |
+ echo "🏷️ Analyzing PR for label suggestions..."
+ echo "## 🏷️ Suggested Labels" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ # Extraire le type du titre
+ if [[ "$PR_TITLE" =~ ^(feat|fix|docs|style|refactor|test|chore) ]]; then
+ TYPE="${BASH_REMATCH[1]}"
+ echo "📌 Type: \`$TYPE\`" >> $GITHUB_STEP_SUMMARY
+
+ case $TYPE in
+ "feat")
+ echo "- \`enhancement\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`feature\`" >> $GITHUB_STEP_SUMMARY
+ ;;
+ "fix")
+ echo "- \`bug\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`fix\`" >> $GITHUB_STEP_SUMMARY
+ ;;
+ "docs")
+ echo "- \`documentation\`" >> $GITHUB_STEP_SUMMARY
+ ;;
+ "test")
+ echo "- \`testing\`" >> $GITHUB_STEP_SUMMARY
+ ;;
+ "refactor")
+ echo "- \`refactoring\`" >> $GITHUB_STEP_SUMMARY
+ ;;
+ "style")
+ echo "- \`style\`" >> $GITHUB_STEP_SUMMARY
+ ;;
+ "chore")
+ echo "- \`maintenance\`" >> $GITHUB_STEP_SUMMARY
+ ;;
+ esac
+ fi
+
+ # Extraire le scope du titre
+ if [[ "$PR_TITLE" =~ \(([a-z0-9-]+)\) ]]; then
+ SCOPE="${BASH_REMATCH[1]}"
+ echo "🎯 Scope: \`$SCOPE\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`$SCOPE\`" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # Analyser le contenu pour des labels supplémentaires
+ CONTENT="$PR_TITLE $PR_BODY"
+
+ if [[ $CONTENT =~ (test|testing|unit.test|integration.test) ]]; then
+ echo "- \`testing\`" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [[ $CONTENT =~ (performance|benchmark|optimization|faster) ]]; then
+ echo "- \`performance\`" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [[ $CONTENT =~ (breaking.change|breaking|major) ]]; then
+ echo "- \`breaking-change\`" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [[ $CONTENT =~ (security|vulnerability|CVE) ]]; then
+ echo "- \`security\`" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "💡 **Note**: Les labels doivent être ajoutés manuellement par un maintainer ayant les permissions appropriées." >> $GITHUB_STEP_SUMMARY
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14baab1..c8417c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.0.2] - 2026-01-09
+
+### Added
+#### Algorithmes et Utilitaires
+- **Fisher-Yates Shuffle** - Algorithme de mélange aléatoire déterministe de tableaux
+ - Fonction standalone `fisherYatesShuffle()` exportée
+ - Support des générateurs aléatoires personnalisés (RNG)
+ - Tests unitaires complets avec validation du déterminisme
+ - Définitions TypeScript complètes
+
+#### Améliorations Soundex
+- **Support des cartes personnalisées** - Possibilité de passer des mappings de caractères personnalisés
+ - Paramètre `customMap` pour définir des encodages phonétiques spécifiques
+ - Priorité donnée aux custom maps sur les maps de langue
+ - Tests de validation des mappings personnalisés
+- **Support multilingue étendu** - Amélioration de la normalisation pour le français
+ - Normalisation des caractères accentués (é, è, ê, à, ù, etc.)
+ - Gestion du ç → s et œ → e
+ - Mappings spécifiques français (F et V → 7 au lieu de 1)
+ - Tests pour tous les cas de normalisation
+
+### Enhanced
+- **Documentation README**
+ - Ajout d'un tableau de performance pour tous les algorithmes
+ - Section "Fonctionnalités" complète avec toutes les capacités de la bibliothèque
+ - Benchmarks détaillés (ops/s) pour petites, moyennes et grandes chaînes
+ - Performance du RandomEngine avec toutes ses fonctions
+- **Tests unitaires** - 152 tests passant (amélioration de la couverture)
+- **TypeScript** - Définitions mises à jour pour fisherYatesShuffle et Soundex
+
+### Fixed
+- **Soundex** - Correction de la logique pour les custom maps
+ - Le code de la première lettre est maintenant inclus uniquement avec customMap
+ - Tests corrigés pour Alfred/Olivier avec mappings français
+ - Conversion String() pour les codes numériques
+
## [1.0.1] - 2025-08-05
### Added
diff --git a/README.md b/README.md
index f42e521..d075232 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,99 @@
[](https://www.npmjs.com/package/algorith)
[](https://opensource.org/licenses/MIT)
-[](./test/)
+[](./test/)
> Collection complète d'algorithmes de similarité textuelle et moteur de génération aléatoire avancé
+## ✨ Fonctionnalités
+
+### 🔍 Algorithmes de Similarité Textuelle
+- **Levenshtein** - Distance d'édition avec insertions, suppressions et substitutions
+- **Jaro-Winkler** - Optimisé pour les préfixes communs (noms propres)
+- **Jaro** - Version de base sans bonus de préfixe
+- **Hamming** - Comparaison caractère par caractère (même longueur)
+- **Jaccard** - Similarité basée sur les ensembles de caractères
+- **Cosine** - Similarité cosinus des vecteurs de fréquence
+- **Dice Coefficient** - Basé sur les bigrammes communs
+- **Trigram Score** - Score de similarité par trigrammes
+- **Soundex** - Encodage phonétique (support multilingue: EN, FR)
+
+### 🎲 Génération Aléatoire (RandomEngine)
+#### Fonctions de Base
+- `uniform()` - Nombres aléatoires uniformes
+- `int()` - Entiers aléatoires dans un intervalle
+- `bool()` - Booléens aléatoires avec probabilité configurable
+- `pick()` - Sélection aléatoire d'un élément dans un tableau
+- `shuffle()` - Mélange Fisher-Yates d'un tableau
+
+#### Distributions Probabilistes
+- `normal()` - Distribution normale (Gaussienne)
+- `exponential()` - Distribution exponentielle
+- `poisson()` - Distribution de Poisson
+- `binomial()` - Distribution binomiale
+- `geometric()` - Distribution géométrique
+- `weighted()` - Sélection pondérée
+
+#### Génération de Texte
+- `randomChar()` - Caractères aléatoires (avec jeux de caractères personnalisés)
+- `randomString()` - Chaînes aléatoires de longueur donnée
+- `randomWord()` - Mots aléatoires basés sur des syllabes
+- `uuid()` - Génération d'UUID v4
+
+#### Fonctions de Bruit
+- `perlin1D()`, `perlin2D()`, `perlin3D()` - Bruit de Perlin (1D, 2D, 3D)
+- `valueNoise()` - Bruit de valeur
+- `whiteNoise()` - Bruit blanc
+- `pinkNoise()` - Bruit rose (1/f)
+
+#### Crypto Sécurisé
+- `cryptoInt()` - Entiers cryptographiquement sécurisés
+
+### 🔤 Autocomplétion Intelligente
+- **Recherche rapide** - Structure Trie pour recherches O(m)
+- **Support multilingue** - Dictionnaires français et anglais intégrés
+- **Extensible** - Ajout facile de dictionnaires personnalisés
+- **API simple** - `autocomplete()`, `addWord()`, `addWords()`
+
+### 🔧 Utilitaires
+- **fisherYatesShuffle** - Mélange aléatoire déterministe de tableaux
+- **compareAll** - Compare deux chaînes avec tous les algorithmes simultanément
+
+## ⚡ Performance des Algorithmes
+
+Benchmarks effectués sur Node.js v24.5.0 (Linux x64)
+
+### Algorithmes de Similarité
+
+| Algorithme | Petites chaînes
(3-5 car.) | Chaînes moyennes
(20-30 car.) | Grandes chaînes
(100-200 car.) |
+| ---------------- | ----------------------------- | -------------------------------- | --------------------------------- |
+| **Hamming** | **720,599 ops/s** | **535,742 ops/s** | **1,230,436 ops/s** |
+| **Jaro-Winkler** | **334,056 ops/s** | **492,129 ops/s** | **126,682 ops/s** |
+| **Jaro** | 159,534 ops/s | 300,080 ops/s | 119,637 ops/s |
+| **Trigram** | 171,536 ops/s | 337,487 ops/s | 170,423 ops/s |
+| **Dice** | 157,987 ops/s | 163,419 ops/s | 36,190 ops/s |
+| **Jaccard** | 119,827 ops/s | 121,730 ops/s | 73,290 ops/s |
+| **Cosine** | 95,908 ops/s | 120,913 ops/s | 59,148 ops/s |
+| **Levenshtein** | 33,657 ops/s | 42,996 ops/s | 12,548 ops/s |
+
+**compareAll()** : 13,316 ops/s (compare avec tous les algorithmes simultanément)
+
+### RandomEngine
+
+| Fonction | Performance |
+| ------------------ | ---------------- |
+| `uniform()` | 12,330,231 ops/s |
+| `perlin1D()` | 22,104,201 ops/s |
+| `bool()` | 16,819,989 ops/s |
+| `whiteNoise()` | 16,051,877 ops/s |
+| `int(1, 100)` | 14,266,552 ops/s |
+| `exponential(1)` | 6,782,895 ops/s |
+| `normal(0, 1)` | 4,002,269 ops/s |
+| `randomWord(5)` | 559,416 ops/s |
+| `randomString(10)` | 287,464 ops/s |
+
+> **Note** : Les performances peuvent varier selon votre environnement d'exécution.
+
## 📦 Installation
```bash
@@ -21,7 +110,8 @@ const {
hamming,
compareAll,
RandomEngine,
- AutocompleteEngine
+ AutocompleteEngine,
+ fisherYatesShuffle
} = require('algorith');
// Comparaison de similarité
@@ -53,6 +143,11 @@ console.log(rng.randomWord()); // "bakaru"
const autocomplete = new AutocompleteEngine({ language: 'fr' });
autocomplete.addWords(['javascript', 'java', 'python']);
console.log(autocomplete.autocomplete('java')); // ['java', 'javascript']
+
+// Mélange Fisher-Yates
+const numbers = [1, 2, 3, 4, 5];
+const shuffled = fisherYatesShuffle(numbers);
+console.log(shuffled); // [3, 1, 5, 2, 4]
```
## 📚 API Documentation
@@ -170,20 +265,48 @@ trigramScore('abc', 'xyz'); // 0.0
**Cas d'usage :** Analyse de séquences, comparaison de texte long.
-#### `soundex(string)`
+#### `soundex(string, language = 'en', customMap = null)`
-Génère le code Soundex d'une chaîne (algorithme phonétique).
+Génère le code Soundex d'une chaîne (algorithme phonétique) avec support multilingue.
+
+**Paramètres :**
+- `string` : La chaîne à encoder
+- `language` : Langue pour les règles spécifiques ('en' ou 'fr', défaut: 'en')
+- `customMap` : Carte de correspondance personnalisée (optionnel)
```javascript
const { soundex } = require('algorith');
+// Usage basique (anglais par défaut)
soundex('Robert'); // 'R163'
soundex('Rupert'); // 'R163' (même son)
soundex('Smith'); // 'S530'
soundex('Smyth'); // 'S530' (même son)
+
+// Support français avec normalisation des accents
+soundex('François', 'fr'); // 'F652'
+soundex('Pierre', 'fr'); // 'P600'
+soundex('Céline', 'fr'); // 'C450'
+
+// Les accents sont automatiquement normalisés en français
+soundex('François', 'fr') === soundex('Francois', 'fr'); // true
+
+// Carte personnalisée
+const customMap = {
+ a: '', e: '', i: '', o: '', u: '',
+ b: 9, p: 9, f: 9, v: 9, // Groupement personnalisé
+ c: 8, k: 8, g: 8
+};
+soundex('Boat', 'en', customMap); // 'B900'
```
-**Cas d'usage :** Recherche phonétique, matching de noms.
+**Fonctionnalités :**
+- **Support multilingue** : Règles spécifiques pour l'anglais et le français
+- **Normalisation française** : Gestion automatique des accents (é→e, ç→s, œ→e)
+- **Cartes personnalisées** : Définition de vos propres règles de correspondance
+- **Compatibilité** : Fonctionne avec l'algorithme Soundex standard
+
+**Cas d'usage :** Recherche phonétique, matching de noms, détection de doublons phonétiques, indexation par similarité sonore.
#### `compareAll(stringA, stringB)`
@@ -486,6 +609,20 @@ rng.fade(0.5); // Fonction de lissage
rng.lerp(0, 10, 0.5); // Interpolation linéaire → 5
```
+### 🔀 Mélange Fisher-Yates
+
+Mélange un tableau en utilisant l'algorithme de Fisher-Yates.
+
+```javascript
+const { fisherYatesShuffle } = require('algorith');
+
+const items = ['a', 'b', 'c', 'd'];
+const shuffled = fisherYatesShuffle(items);
+
+console.log(items); // ['a', 'b', 'c', 'd'] (non modifié)
+console.log(shuffled); // Mélange aléatoire
+```
+
## 🎯 Exemples d'Usage
### Détection de Doublons
@@ -579,7 +716,7 @@ const map = generateTerrain(100, 100);
## 🧪 Tests
-Le module inclut 114 tests complets :
+Le module inclut 115 tests complets :
```bash
# Exécuter tous les tests
diff --git a/algorithms/fisherYatesShuffle.js b/algorithms/fisherYatesShuffle.js
new file mode 100644
index 0000000..ffc6a96
--- /dev/null
+++ b/algorithms/fisherYatesShuffle.js
@@ -0,0 +1,10 @@
+module.exports = function fisherYatesShuffle(array, random = Math.random) {
+ const result = array.slice();
+
+ for (let i = result.length - 1; i > 0; i--) {
+ const j = Math.floor(random() * (i + 1));
+ [result[i], result[j]] = [result[j], result[i]];
+ }
+
+ return result;
+};
diff --git a/algorithms/soundex.js b/algorithms/soundex.js
index 009263a..d8486a0 100644
--- a/algorithms/soundex.js
+++ b/algorithms/soundex.js
@@ -1,43 +1,57 @@
-module.exports = function soundex(s) {
+module.exports = function soundex(s, lang = 'en', customMap = null) {
if (!s || s.length === 0) return 'Z000';
-
- const map = {
- a: '',
- e: '',
- i: '',
- o: '',
- u: '',
- b: 1,
- f: 1,
- p: 1,
- v: 1,
- c: 2,
- g: 2,
- j: 2,
- k: 2,
- q: 2,
- s: 2,
- x: 2,
- z: 2,
- d: 3,
- t: 3,
- l: 4,
- m: 5,
- n: 5,
- r: 6
+
+ // Normalisation pour les lettres accentuées (français)
+ const normalize = (str) =>
+ str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/ç/g, "s").replace(/œ/g, "e");
+
+ // Tables intégrées
+ const maps = {
+ en: {
+ a: '', e: '', i: '', o: '', u: '', y: '', h: '', w: '',
+ b: 1, f: 1, p: 1, v: 1,
+ c: 2, g: 2, j: 2, k: 2, q: 2, s: 2, x: 2, z: 2,
+ d: 3, t: 3,
+ l: 4,
+ m: 5, n: 5,
+ r: 6
+ },
+ fr: {
+ a: '', e: '', i: '', o: '', u: '', y: '', h: '', w: '',
+ b: 1, p: 1,
+ c: 2, k: 2, q: 2, g: 2, j: 2, s: 2, x: 2, z: 2, ç: 2,
+ d: 3, t: 3,
+ l: 4,
+ m: 5, n: 5,
+ r: 6,
+ f: 7, v: 7
+ }
};
+ // Prétraitement
s = s.toLowerCase();
- let first = s[0].toUpperCase();
+ if (lang === 'fr') s = normalize(s);
+
+ // Sélection de la map
+ const map = customMap || maps[lang] || maps['en'];
+
+ // Initialisation
+ const first = s[0].toUpperCase();
let code = first;
- let prev = map[s[0]] || '';
+ // Pour les maps personnalisées uniquement, inclure le code de la première lettre
+ const firstDigit = map[s[0]] ?? '';
+ if (customMap && firstDigit !== '') {
+ code += String(firstDigit);
+ }
+
+ let prev = map[s[0]] ?? '';
for (let i = 1; i < s.length; i++) {
- const digit = map[s[i]] || '';
- if (digit !== prev) code += digit;
+ const digit = map[s[i]] ?? '';
+ if (digit !== '' && digit !== prev) code += String(digit);
if (code.length === 4) break;
if (digit !== '') prev = digit;
}
return code.padEnd(4, '0');
-};
\ No newline at end of file
+};
diff --git a/docs/PR_CONVENTIONS.md b/docs/PR_CONVENTIONS.md
new file mode 100644
index 0000000..dfe06fb
--- /dev/null
+++ b/docs/PR_CONVENTIONS.md
@@ -0,0 +1,168 @@
+# 📋 Guide des Conventions de Pull Request
+
+## 🎯 Format des Titres de PR
+
+Tous les titres de Pull Request doivent suivre le format des **Conventional Commits** :
+
+```
+type(scope): description
+```
+
+### 🔧 Types Valides
+
+| Type | Description | Exemple |
+| ---------- | -------------------------------------------- | ------------------------------------------------ |
+| `feat` | Nouvelle fonctionnalité | `feat(autocomplete): add Trie-based engine` |
+| `fix` | Correction de bug | `fix(levenshtein): correct distance calculation` |
+| `docs` | Documentation uniquement | `docs(readme): update API examples` |
+| `style` | Formatage, style (pas de changement logique) | `style(algorithms): fix indentation` |
+| `refactor` | Refactoring sans changement fonctionnel | `refactor(random): simplify seed generation` |
+| `test` | Ajout ou modification de tests | `test(similarity): add edge cases` |
+| `chore` | Tâches de maintenance | `chore(deps): update dependencies` |
+
+### 🎯 Scopes Recommandés
+
+| Scope | Description |
+| -------------- | ------------------------------ |
+| `autocomplete` | Moteur d'autocomplétion |
+| `similarity` | Algorithmes de similarité |
+| `random` | Moteur de génération aléatoire |
+| `levenshtein` | Algorithme Levenshtein |
+| `jaro` | Algorithmes Jaro/Jaro-Winkler |
+| `hamming` | Distance de Hamming |
+| `jaccard` | Similarité de Jaccard |
+| `cosine` | Similarité cosinus |
+| `dice` | Coefficient de Dice |
+| `soundex` | Algorithme Soundex |
+| `trigram` | Score de trigrammes |
+| `readme` | Documentation README |
+| `examples` | Fichier EXAMPLES.md |
+| `types` | Définitions TypeScript |
+| `tests` | Tests globaux |
+| `benchmark` | Benchmarks de performance |
+| `ci` | Intégration continue |
+| `deps` | Dépendances |
+
+## ✅ Exemples de Titres Valides
+
+```bash
+# Nouvelles fonctionnalités
+feat(autocomplete): add multilingual dictionary support
+feat(similarity): implement new fuzzy matching algorithm
+feat(random): add exponential distribution
+
+# Corrections de bugs
+fix(levenshtein): handle empty string edge case
+fix(jaro): correct prefix scaling calculation
+fix(random): ensure deterministic seed behavior
+
+# Documentation
+docs(readme): add performance benchmarks section
+docs(examples): update autocomplete usage examples
+docs(api): clarify method return types
+
+# Tests
+test(autocomplete): add performance tests for large dictionaries
+test(similarity): cover unicode character handling
+test(integration): add end-to-end workflow tests
+
+# Refactoring
+refactor(algorithms): extract common utility functions
+refactor(types): improve TypeScript definitions clarity
+refactor(exports): standardize module exports
+
+# Maintenance
+chore(deps): update mocha to latest version
+chore(ci): improve GitHub Actions performance
+chore(lint): fix ESLint configuration
+```
+
+## ❌ Exemples de Titres Invalides
+
+```bash
+# Trop vague
+❌ Update code
+❌ Fix bug
+❌ Add feature
+
+# Format incorrect
+❌ Add: new autocomplete feature
+❌ FIX(similarity) - correct calculation
+❌ feat/autocomplete: new engine
+
+# Casse incorrecte
+❌ FEAT(autocomplete): Add new engine
+❌ Feat(Autocomplete): Add New Engine
+❌ feat(autocomplete): Add new engine # description doit être en minuscules
+
+# Types invalides
+❌ add(autocomplete): new feature # utiliser 'feat'
+❌ update(docs): readme changes # utiliser 'docs'
+❌ bugfix(random): seed issue # utiliser 'fix'
+```
+
+## 🔍 Validation Automatique
+
+Un workflow GitHub Actions valide automatiquement le format des titres de PR. Si le titre ne respecte pas les conventions :
+
+1. ❌ La validation échoue
+2. 📋 Un résumé détaillé explique le problème
+3. 💡 Des exemples corrects sont suggérés
+4. 🏷️ Des labels appropriés sont recommandés
+
+## 🛠️ Outils de Validation
+
+### Script Local
+```bash
+# Valider un titre localement
+./scripts/validate-pr-title.sh "feat(autocomplete): add new engine"
+```
+
+### Avant de Créer une PR
+```bash
+# Utiliser le script de création automatique
+./scripts/create-pr-autocomplete.sh
+```
+
+## 📋 Checklist pour les PR
+
+Avant de soumettre votre Pull Request :
+
+- [ ] Le titre respecte le format `type(scope): description`
+- [ ] Le type est l'un des types valides
+- [ ] Le scope correspond au composant modifié
+- [ ] La description est concise et en minuscules
+- [ ] La description commence par un verbe à l'infinitif
+- [ ] Tous les tests passent
+- [ ] La documentation est mise à jour si nécessaire
+- [ ] Le CHANGELOG.md est mis à jour pour les changements significatifs
+
+## 🎯 Bonnes Pratiques
+
+### Description
+- Utiliser l'infinitif : "add", "fix", "update", pas "adds", "fixed", "updated"
+- Être concis mais descriptif
+- Éviter les points à la fin
+- Maximum 50 caractères
+
+### Scope
+- Utiliser des noms courts et clairs
+- Préférer les noms de composants aux noms de fichiers
+- Rester cohérent avec les scopes existants
+
+### Type
+- `feat` pour toute nouvelle fonctionnalité visible par l'utilisateur
+- `fix` pour toute correction de comportement incorrect
+- `docs` uniquement pour les changements de documentation
+- `refactor` pour les changements qui n'affectent pas le comportement
+
+## 🚀 Automatisation
+
+Le repository inclut des outils pour faciliter le respect des conventions :
+
+1. **Script de validation** : `scripts/validate-pr-title.sh`
+2. **Script de création PR** : `scripts/create-pr-autocomplete.sh`
+3. **Workflow de validation** : `.github/workflows/pr-validation.yml`
+4. **Template de PR** : `.github/PULL_REQUEST_TEMPLATE.md`
+
+Ces outils garantissent une cohérence dans tout le projet et facilitent la maintenance du changelog automatique.
diff --git a/index.d.ts b/index.d.ts
index 9e2d56e..9980a71 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -3,19 +3,19 @@
// Definitions by: MXA.K
export interface SimilarityResult {
- levenshtein: number;
- jaroWinkler: number;
- hamming: number;
- trigram: number;
- jaccard: number;
- jaro: number;
- dice: number;
- cosine: number;
+ levenshtein: number;
+ jaroWinkler: number;
+ hamming: number;
+ trigram: number;
+ jaccard: number;
+ jaro: number;
+ dice: number;
+ cosine: number;
}
export interface WeightedItem {
- value: T;
- weight: number;
+ value: T;
+ weight: number;
}
/**
@@ -83,11 +83,21 @@ export function diceCoefficient(a: string, b: string): number;
export function trigramScore(a: string, b: string): number;
/**
- * Generates the Soundex code for a string
+ * Shuffles an array using the Fisher-Yates algorithm
+ * @param array Input array
+ * @param random Optional random generator (default: Math.random)
+ * @returns New shuffled array
+ */
+export function fisherYatesShuffle(array: T[], random?: () => number): T[];
+
+/**
+ * Generates the Soundex code for a string with multilingual support
* @param s Input string
+ * @param lang Language code ('en' or 'fr', default: 'en')
+ * @param customMap Custom character mapping (optional)
* @returns 4-character Soundex code
*/
-export function soundex(s: string): string;
+export function soundex(s: string, lang?: string, customMap?: Record): string;
/**
* Compares two strings using all available similarity algorithms
@@ -101,272 +111,272 @@ export function compareAll(a: string, b: string): SimilarityResult;
* Advanced random number generator with multiple distributions and noise functions
*/
export class RandomEngine {
- /**
- * Current seed value
- */
- public seed: number;
-
- /**
- * Gradient table for noise generation
- */
- public gradients: number[];
-
- /**
- * Permutation table for noise generation
- */
- public permutation: number[];
-
- /**
- * Creates a new RandomEngine instance
- * @param seed Optional seed for deterministic generation
- */
- constructor(seed?: number);
-
- // Basic random functions
-
- /**
- * Generates a uniform random number
- * @param min Minimum value (default: 0)
- * @param max Maximum value (default: 1)
- * @returns Random number between min and max
- */
- uniform(min?: number, max?: number): number;
-
- /**
- * Generates a random integer
- * @param min Minimum value (inclusive)
- * @param max Maximum value (inclusive)
- * @returns Random integer between min and max
- */
- int(min: number, max: number): number;
-
- /**
- * Generates a random boolean
- * @param prob Probability of returning true (default: 0.5)
- * @returns Random boolean value
- */
- bool(prob?: number): boolean;
-
- /**
- * Picks a random element from an array
- * @param array Input array
- * @returns Random element from the array
- */
- pick(array: T[]): T;
-
- /**
- * Shuffles an array (returns a new array)
- * @param array Input array
- * @returns New shuffled array
- */
- shuffle(array: T[]): T[];
-
- // Probability distributions
-
- /**
- * Generates a normally distributed random number
- * @param mean Mean value (default: 0)
- * @param stdDev Standard deviation (default: 1)
- * @returns Normally distributed random number
- */
- normal(mean?: number, stdDev?: number): number;
-
- /**
- * Generates an exponentially distributed random number
- * @param lambda Rate parameter (default: 1)
- * @returns Exponentially distributed random number
- */
- exponential(lambda?: number): number;
-
- /**
- * Generates a Poisson distributed random number
- * @param lambda Rate parameter (default: 4)
- * @returns Poisson distributed random integer
- */
- poisson(lambda?: number): number;
-
- /**
- * Generates a binomially distributed random number
- * @param n Number of trials
- * @param p Probability of success
- * @returns Binomially distributed random integer
- */
- binomial(n: number, p: number): number;
-
- /**
- * Generates a geometrically distributed random number
- * @param p Probability of success
- * @returns Geometrically distributed random integer
- */
- geometric(p: number): number;
-
- /**
- * Selects a random value based on weights
- * @param items Array of weighted items
- * @returns Selected value
- */
- weighted(items: WeightedItem[]): T;
-
- // Text generation
-
- /**
- * Generates a random lowercase character
- * @returns Random character a-z
- */
- randomChar(): string;
-
- /**
- * Generates a random string
- * @param length Length of the string (default: 8)
- * @returns Random string
- */
- randomString(length?: number): string;
-
- /**
- * Generates a random pronounceable word
- * @returns Random word made of syllables
- */
- randomWord(): string;
-
- /**
- * Generates a UUID v4
- * @returns Valid UUID string
- */
- uuid(): string;
-
- // Crypto functions
-
- /**
- * Generates a cryptographically secure random integer
- * @param min Minimum value (inclusive)
- * @param max Maximum value (inclusive)
- * @returns Cryptographically secure random integer
- */
- static cryptoInt(min: number, max: number): number;
-
- // Noise functions
-
- /**
- * Generates noise using the specified type
- * @param x Input coordinate
- * @param type Noise type: 'perlin', 'value', 'white', or 'pink'
- * @returns Noise value
- */
- noise(x: number, type?: 'perlin' | 'value' | 'white' | 'pink'): number;
-
- /**
- * Generates 1D Perlin noise
- * @param x Input coordinate
- * @returns Perlin noise value
- */
- perlin1D(x: number): number;
-
- /**
- * Generates 1D value noise
- * @param x Input coordinate
- * @returns Value noise value
- */
- valueNoise1D(x: number): number;
-
- /**
- * Generates white noise
- * @returns White noise value between -1 and 1
- */
- whiteNoise(): number;
-
- /**
- * Generates pink noise
- * @param x Input frequency
- * @returns Pink noise value
- */
- pinkNoise(x: number): number;
-
- // Utility functions
-
- /**
- * Applies fade function for smooth interpolation
- * @param t Input value between 0 and 1
- * @returns Smoothed value
- */
- fade(t: number): number;
-
- /**
- * Linear interpolation between two values
- * @param a First value
- * @param b Second value
- * @param t Interpolation factor (0-1)
- * @returns Interpolated value
- */
- lerp(a: number, b: number, t: number): number;
-
- /**
- * Generates gradient table for noise functions
- * @param size Size of the gradient table (default: 256)
- */
- generateGradientTable(size?: number): void;
-
- /**
- * Mulberry32 PRNG implementation
- * @param seed Seed value
- * @returns Random number generator function
- */
- mulberry32(seed: number): () => number;
+ /**
+ * Current seed value
+ */
+ public seed: number;
+
+ /**
+ * Gradient table for noise generation
+ */
+ public gradients: number[];
+
+ /**
+ * Permutation table for noise generation
+ */
+ public permutation: number[];
+
+ /**
+ * Creates a new RandomEngine instance
+ * @param seed Optional seed for deterministic generation
+ */
+ constructor(seed?: number);
+
+ // Basic random functions
+
+ /**
+ * Generates a uniform random number
+ * @param min Minimum value (default: 0)
+ * @param max Maximum value (default: 1)
+ * @returns Random number between min and max
+ */
+ uniform(min?: number, max?: number): number;
+
+ /**
+ * Generates a random integer
+ * @param min Minimum value (inclusive)
+ * @param max Maximum value (inclusive)
+ * @returns Random integer between min and max
+ */
+ int(min: number, max: number): number;
+
+ /**
+ * Generates a random boolean
+ * @param prob Probability of returning true (default: 0.5)
+ * @returns Random boolean value
+ */
+ bool(prob?: number): boolean;
+
+ /**
+ * Picks a random element from an array
+ * @param array Input array
+ * @returns Random element from the array
+ */
+ pick(array: T[]): T;
+
+ /**
+ * Shuffles an array (returns a new array)
+ * @param array Input array
+ * @returns New shuffled array
+ */
+ shuffle(array: T[]): T[];
+
+ // Probability distributions
+
+ /**
+ * Generates a normally distributed random number
+ * @param mean Mean value (default: 0)
+ * @param stdDev Standard deviation (default: 1)
+ * @returns Normally distributed random number
+ */
+ normal(mean?: number, stdDev?: number): number;
+
+ /**
+ * Generates an exponentially distributed random number
+ * @param lambda Rate parameter (default: 1)
+ * @returns Exponentially distributed random number
+ */
+ exponential(lambda?: number): number;
+
+ /**
+ * Generates a Poisson distributed random number
+ * @param lambda Rate parameter (default: 4)
+ * @returns Poisson distributed random integer
+ */
+ poisson(lambda?: number): number;
+
+ /**
+ * Generates a binomially distributed random number
+ * @param n Number of trials
+ * @param p Probability of success
+ * @returns Binomially distributed random integer
+ */
+ binomial(n: number, p: number): number;
+
+ /**
+ * Generates a geometrically distributed random number
+ * @param p Probability of success
+ * @returns Geometrically distributed random integer
+ */
+ geometric(p: number): number;
+
+ /**
+ * Selects a random value based on weights
+ * @param items Array of weighted items
+ * @returns Selected value
+ */
+ weighted(items: WeightedItem[]): T;
+
+ // Text generation
+
+ /**
+ * Generates a random lowercase character
+ * @returns Random character a-z
+ */
+ randomChar(): string;
+
+ /**
+ * Generates a random string
+ * @param length Length of the string (default: 8)
+ * @returns Random string
+ */
+ randomString(length?: number): string;
+
+ /**
+ * Generates a random pronounceable word
+ * @returns Random word made of syllables
+ */
+ randomWord(): string;
+
+ /**
+ * Generates a UUID v4
+ * @returns Valid UUID string
+ */
+ uuid(): string;
+
+ // Crypto functions
+
+ /**
+ * Generates a cryptographically secure random integer
+ * @param min Minimum value (inclusive)
+ * @param max Maximum value (inclusive)
+ * @returns Cryptographically secure random integer
+ */
+ static cryptoInt(min: number, max: number): number;
+
+ // Noise functions
+
+ /**
+ * Generates noise using the specified type
+ * @param x Input coordinate
+ * @param type Noise type: 'perlin', 'value', 'white', or 'pink'
+ * @returns Noise value
+ */
+ noise(x: number, type?: 'perlin' | 'value' | 'white' | 'pink'): number;
+
+ /**
+ * Generates 1D Perlin noise
+ * @param x Input coordinate
+ * @returns Perlin noise value
+ */
+ perlin1D(x: number): number;
+
+ /**
+ * Generates 1D value noise
+ * @param x Input coordinate
+ * @returns Value noise value
+ */
+ valueNoise1D(x: number): number;
+
+ /**
+ * Generates white noise
+ * @returns White noise value between -1 and 1
+ */
+ whiteNoise(): number;
+
+ /**
+ * Generates pink noise
+ * @param x Input frequency
+ * @returns Pink noise value
+ */
+ pinkNoise(x: number): number;
+
+ // Utility functions
+
+ /**
+ * Applies fade function for smooth interpolation
+ * @param t Input value between 0 and 1
+ * @returns Smoothed value
+ */
+ fade(t: number): number;
+
+ /**
+ * Linear interpolation between two values
+ * @param a First value
+ * @param b Second value
+ * @param t Interpolation factor (0-1)
+ * @returns Interpolated value
+ */
+ lerp(a: number, b: number, t: number): number;
+
+ /**
+ * Generates gradient table for noise functions
+ * @param size Size of the gradient table (default: 256)
+ */
+ generateGradientTable(size?: number): void;
+
+ /**
+ * Mulberry32 PRNG implementation
+ * @param seed Seed value
+ * @returns Random number generator function
+ */
+ mulberry32(seed: number): () => number;
}
export interface AutocompleteOptions {
- /** Language for default dictionary ('fr' or 'en') */
- language?: 'fr' | 'en' | string;
- /** Custom dictionary array or path to dictionary file */
- dictionary?: string[] | string | null;
- /** Maximum number of suggestions to return */
- maxSuggestions?: number;
+ /** Language for default dictionary ('fr' or 'en') */
+ language?: 'fr' | 'en' | string;
+ /** Custom dictionary array or path to dictionary file */
+ dictionary?: string[] | string | null;
+ /** Maximum number of suggestions to return */
+ maxSuggestions?: number;
}
/**
* Advanced autocomplete engine using Trie data structure
*/
export class AutocompleteEngine {
- /** Current language setting */
- readonly language: string;
-
- /** Maximum number of suggestions */
- readonly maxSuggestions: number;
-
- /**
- * Creates a new AutocompleteEngine instance
- * @param options Configuration options
- */
- constructor(options?: AutocompleteOptions);
-
- /**
- * Adds a single word to the autocomplete dictionary
- * @param word Word to add (will be normalized to lowercase)
- */
- addWord(word: string): void;
-
- /**
- * Adds multiple words to the autocomplete dictionary
- * @param words Array of words to add
- */
- addWords(words: string[]): void;
-
- /**
- * Gets autocomplete suggestions for a given prefix
- * @param prefix Text prefix to search for
- * @returns Array of suggested completions
- */
- autocomplete(prefix: string): string[];
-
- /**
- * Alias for autocomplete method (for compatibility)
- * @param prefix Text prefix to search for
- * @returns Array of suggested completions
- */
- search(prefix: string): string[];
-
- /**
- * Returns the total number of words in the dictionary
- * @returns Number of words in the dictionary
- */
- getWordCount(): number;
+ /** Current language setting */
+ readonly language: string;
+
+ /** Maximum number of suggestions */
+ readonly maxSuggestions: number;
+
+ /**
+ * Creates a new AutocompleteEngine instance
+ * @param options Configuration options
+ */
+ constructor(options?: AutocompleteOptions);
+
+ /**
+ * Adds a single word to the autocomplete dictionary
+ * @param word Word to add (will be normalized to lowercase)
+ */
+ addWord(word: string): void;
+
+ /**
+ * Adds multiple words to the autocomplete dictionary
+ * @param words Array of words to add
+ */
+ addWords(words: string[]): void;
+
+ /**
+ * Gets autocomplete suggestions for a given prefix
+ * @param prefix Text prefix to search for
+ * @returns Array of suggested completions
+ */
+ autocomplete(prefix: string): string[];
+
+ /**
+ * Alias for autocomplete method (for compatibility)
+ * @param prefix Text prefix to search for
+ * @returns Array of suggested completions
+ */
+ search(prefix: string): string[];
+
+ /**
+ * Returns the total number of words in the dictionary
+ * @returns Number of words in the dictionary
+ */
+ getWordCount(): number;
}
diff --git a/index.js b/index.js
index 6bdff8f..1bc438f 100644
--- a/index.js
+++ b/index.js
@@ -9,6 +9,7 @@ const cosineSimilarity = require('./algorithms/cosineSimilarity');
const soundex = require('./algorithms/soundex');
const RandomEngine = require('./algorithms/RandomEngine');
const AutocompleteEngine = require('./algorithms/autocomplete');
+const fisherYatesShuffle = require('./algorithms/fisherYatesShuffle');
function compareAll(a, b) {
return {
@@ -35,5 +36,6 @@ module.exports = {
soundex,
RandomEngine,
AutocompleteEngine,
+ fisherYatesShuffle,
compareAll
-};
\ No newline at end of file
+};
diff --git a/package-lock.json b/package-lock.json
index bd10b62..2279017 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "algorith",
- "version": "1.0.0",
+ "version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "algorith",
- "version": "1.0.0",
+ "version": "1.0.1",
"license": "MIT",
"devDependencies": {
"mocha": "^10.0.0",
@@ -61,6 +61,7 @@
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -319,9 +320,9 @@
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -584,6 +585,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
@@ -1425,9 +1427,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 5b7127b..d93f84d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "algorith",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "Collection complète d'algorithmes de similarité textuelle et moteur de génération aléatoire avancé",
"main": "index.js",
"types": "index.d.ts",
@@ -49,7 +49,11 @@
"fuzzy-search",
"string-matching",
"nlp",
- "algorithms"
+ "algorithms",
+ "autocomplete",
+ "autocomplete-engine",
+ "autocomplete-algorithm",
+ "autocomplete-suggestions"
],
"engines": {
"node": ">=14.0.0"
diff --git a/test/compareAll.test.js b/test/compareAll.test.js
index 86e089c..2271c4b 100644
--- a/test/compareAll.test.js
+++ b/test/compareAll.test.js
@@ -12,6 +12,7 @@ describe('Compare All Algorithms', () => {
assert(typeof algorithms.jaro === 'function');
assert(typeof algorithms.cosineSimilarity === 'function');
assert(typeof algorithms.RandomEngine === 'function'); // Constructor
+ assert(typeof algorithms.fisherYatesShuffle === 'function');
assert(typeof algorithms.compareAll === 'function');
});
diff --git a/test/fisherYatesShuffle.test.js b/test/fisherYatesShuffle.test.js
new file mode 100644
index 0000000..517c872
--- /dev/null
+++ b/test/fisherYatesShuffle.test.js
@@ -0,0 +1,32 @@
+const assert = require('assert');
+const fisherYatesShuffle = require('../algorithms/fisherYatesShuffle');
+
+describe('Fisher-Yates Shuffle', () => {
+ it('should return a new array without mutating the original', () => {
+ const original = [1, 2, 3, 4];
+ const shuffled = fisherYatesShuffle(original, () => 0);
+
+ assert.deepStrictEqual(original, [1, 2, 3, 4]);
+ assert.notStrictEqual(shuffled, original);
+ });
+
+ it('should keep the same elements', () => {
+ const original = [1, 2, 3, 4, 5];
+ const shuffled = fisherYatesShuffle(original, () => 0.5);
+
+ assert.deepStrictEqual(shuffled.slice().sort((a, b) => a - b), original);
+ });
+
+ it('should produce deterministic output with a custom RNG', () => {
+ const values = [0.1, 0.7, 0.3];
+ let index = 0;
+ const rng = () => values[index++];
+
+ const result = fisherYatesShuffle([1, 2, 3, 4], rng);
+ assert.deepStrictEqual(result, [2, 4, 3, 1]);
+ });
+
+ it('should handle empty arrays', () => {
+ assert.deepStrictEqual(fisherYatesShuffle([], () => 0.5), []);
+ });
+});
diff --git a/test/soundex.test.js b/test/soundex.test.js
index d3654ab..dc648cf 100644
--- a/test/soundex.test.js
+++ b/test/soundex.test.js
@@ -64,10 +64,115 @@ describe('Soundex Algorithm', () => {
const f_code = soundex('F')[1];
const p_code = soundex('P')[1];
const v_code = soundex('V')[1];
-
+
// All should map to '1' or be handled consistently
assert.strictEqual(b_code, f_code);
assert.strictEqual(f_code, p_code);
assert.strictEqual(p_code, v_code);
});
+
+ // Tests pour le support multilingue
+ describe('Multilingual Support', () => {
+ it('should work with English language (default)', () => {
+ assert.strictEqual(soundex('Robert'), 'R163');
+ assert.strictEqual(soundex('Robert', 'en'), 'R163');
+ });
+
+ it('should work with French language', () => {
+ assert.strictEqual(soundex('François', 'fr'), 'F652');
+ assert.strictEqual(soundex('Pierre', 'fr'), 'P600');
+ assert.strictEqual(soundex('Céline', 'fr'), 'C450');
+ });
+
+ it('should handle French accented characters', () => {
+ const result1 = soundex('François', 'fr');
+ const result2 = soundex('Francois', 'fr');
+ assert.strictEqual(result1, result2); // Should normalize accents
+ });
+
+ it('should handle French specific mappings', () => {
+ // In French, F and V map to 7 instead of 1
+ // Test with words where F/V appear after the first letter
+ assert.strictEqual(soundex('Alfred', 'fr')[2], '7'); // A-l-f-r-e-d → A476, [2] = '7'
+ assert.strictEqual(soundex('Olivier', 'fr')[2], '7'); // O-l-i-v-i-e-r → O476, [2] = '7'
+ });
+
+ it('should fallback to English for unknown languages', () => {
+ const englishResult = soundex('Robert', 'en');
+ const unknownResult = soundex('Robert', 'unknown');
+ assert.strictEqual(englishResult, unknownResult);
+ });
+ });
+
+ // Tests pour les cartes personnalisées
+ describe('Custom Mapping', () => {
+ it('should accept custom character mapping', () => {
+ const customMap = {
+ a: '', e: '', i: '', o: '', u: '',
+ b: 9, p: 9, // Custom mapping
+ c: 8, k: 8,
+ d: 7, t: 7
+ };
+
+ const result = soundex('Boat', null, customMap);
+ assert.strictEqual(result[0], 'B');
+ assert.strictEqual(result[1], '9'); // Should use custom mapping
+ });
+
+ it('should prioritize custom map over language map', () => {
+ const customMap = {
+ a: '', e: '', i: '', o: '', u: '',
+ r: 9, // Different from standard mapping
+ o: '', b: 1, e: '', r: 9, t: 3
+ };
+
+ const standardResult = soundex('Robert', 'en');
+ const customResult = soundex('Robert', 'en', customMap);
+ assert.notStrictEqual(standardResult, customResult);
+ });
+ });
+
+ // Tests pour la normalisation française
+ describe('French Normalization', () => {
+ it('should normalize ç to s', () => {
+ const result1 = soundex('François', 'fr');
+ const result2 = soundex('Francois', 'fr');
+ assert.strictEqual(result1, result2);
+ });
+
+ it('should normalize œ to e', () => {
+ const result1 = soundex('Cœur', 'fr');
+ const result2 = soundex('Ceur', 'fr');
+ assert.strictEqual(result1, result2);
+ });
+
+ it('should remove diacritics', () => {
+ const result1 = soundex('Élève', 'fr');
+ const result2 = soundex('Eleve', 'fr');
+ assert.strictEqual(result1, result2);
+ });
+ });
+
+ // Tests de régression
+ describe('Edge Cases', () => {
+ it('should handle null input', () => {
+ assert.strictEqual(soundex(null), 'Z000');
+ });
+
+ it('should handle undefined input', () => {
+ assert.strictEqual(soundex(undefined), 'Z000');
+ });
+
+ it('should handle numbers in strings', () => {
+ const result = soundex('Test123');
+ assert.strictEqual(result.length, 4);
+ assert.strictEqual(result[0], 'T');
+ });
+
+ it('should handle special characters', () => {
+ const result = soundex('Test-Name');
+ assert.strictEqual(result.length, 4);
+ assert.strictEqual(result[0], 'T');
+ });
+ });
});