Introduction au filtrage de Map

Les Map en JavaScript sont des structures de données puissantes qui stockent des paires clé-valeur. Contrairement aux objets classiques, les Map préservent l'ordre d'insertion et peuvent utiliser n'importe quel type comme clé. Cependant, contrairement aux tableaux qui disposent de la méthode filter(), les Map n'ont pas de méthode de filtrage intégrée. Dans ce guide complet, nous allons explorer différentes techniques pour filtrer un Map en JavaScript selon les clés, les valeurs, ou des conditions complexes.

Méthode 1 : Filtrer par valeurs

La méthode la plus courante pour filtrer un Map consiste à convertir les entrées en tableau, filtrer ce tableau, puis créer un nouveau Map. Commençons par filtrer selon les valeurs :

// Créer un Map
const monMap = new Map([
  ['pomme', 5],
  ['banane', 3],
  ['cerise', 8],
  ['datte', 2],
  ['ananas', 10]
]);

// Filtrer les fruits avec plus de 5 unités
const fruitsFiltres = new Map(
  [...monMap.entries()].filter(([cle, valeur]) => valeur > 5)
);

console.log(fruitsFiltres);
// Map(2) { 'cerise' => 8, 'ananas' => 10 }

// Filtrer les fruits avec moins de 4 unités
const fruitsRares = new Map(
  [...monMap.entries()].filter(([cle, valeur]) => valeur < 4)
);

console.log(fruitsRares);
// Map(2) { 'banane' => 3, 'datte' => 2 }

Explication

L'opérateur de décomposition [...monMap.entries()] convertit le Map en tableau de paires [clé, valeur]. La méthode filter() prend une fonction de callback qui reçoit chaque entrée (déstructurée en [cle, valeur]) et retourne true pour conserver l'élément ou false pour l'exclure. Enfin, nous créons un nouveau Map à partir du tableau filtré.

Méthode 2 : Filtrer par clés

Vous pouvez également filtrer un Map en fonction des clés plutôt que des valeurs :

// Filtrer les fruits dont le nom commence par 'c'
const fruitsEnC = new Map(
  [...monMap.entries()].filter(([cle, valeur]) => cle.startsWith('c'))
);

console.log(fruitsEnC);
// Map(1) { 'cerise' => 8 }

// Filtrer les fruits dont le nom contient 'an'
const fruitsAvecAn = new Map(
  [...monMap.entries()].filter(([cle, valeur]) => cle.includes('an'))
);

console.log(fruitsAvecAn);
// Map(2) { 'banane' => 3, 'ananas' => 10 }

// Filtrer avec une expression régulière
const fruitsLongs = new Map(
  [...monMap.entries()].filter(([cle, valeur]) => cle.length > 5)
);

console.log(fruitsLongs);
// Map(2) { 'banane' => 3, 'cerise' => 8, 'ananas' => 10 }

Méthode 3 : Filtrer avec des conditions complexes

Vous pouvez combiner plusieurs conditions pour créer des filtres plus complexes :

// Filtrer les fruits avec valeur > 5 ET nom commençant par 'a'
const fruitsFiltresComplexes = new Map(
  [...monMap.entries()].filter(([cle, valeur]) => 
    valeur > 5 && cle.startsWith('a')
  )
);

console.log(fruitsFiltresComplexes);
// Map(1) { 'ananas' => 10 }

// Filtrer les fruits avec valeur entre 3 et 7 OU nom de 6 caractères
const fruitsMultiConditions = new Map(
  [...monMap.entries()].filter(([cle, valeur]) => 
    (valeur >= 3 && valeur <= 7) || cle.length === 6
  )
);

console.log(fruitsMultiConditions);
// Map(3) { 'banane' => 3, 'cerise' => 8, 'datte' => 2 }

Créer une fonction réutilisable

Pour une meilleure organisation du code, créons une fonction utilitaire générique :

function filtrerMap(map, predicat) {
  return new Map(
    [...map.entries()].filter(([cle, valeur]) => predicat(cle, valeur))
  );
}

// Exemples d'utilisation

// Filtrer par valeur
const fruitsChers = filtrerMap(monMap, (cle, valeur) => valeur > 5);

// Filtrer par clé
const fruitsAvecA = filtrerMap(monMap, (cle, valeur) => cle.includes('a'));

// Filtrer avec condition complexe
const fruitsSpeciaux = filtrerMap(monMap, (cle, valeur) => 
  valeur >= 3 && cle.length > 5
);

Fonction avancée avec options

Voici une version plus avancée qui permet de filtrer par clé, valeur, ou les deux :

function filtrerMapAvance(map, options = {}) {
  const {
    filtreCle = null,        // Fonction pour filtrer par clé
    filtreValeur = null,     // Fonction pour filtrer par valeur
    filtreComplet = null     // Fonction pour filtrer avec clé et valeur
  } = options;

  const predicat = (cle, valeur) => {
    // Si un filtre complet est fourni, l'utiliser
    if (filtreComplet) {
      return filtreComplet(cle, valeur);
    }

    // Sinon, combiner les filtres de clé et valeur
    const passeCle = filtreCle ? filtreCle(cle) : true;
    const passeValeur = filtreValeur ? filtreValeur(valeur) : true;

    return passeCle && passeValeur;
  };

  return new Map(
    [...map.entries()].filter(([cle, valeur]) => predicat(cle, valeur))
  );
}

// Utilisation

// Filtrer uniquement par valeur
const parValeur = filtrerMapAvance(monMap, {
  filtreValeur: (valeur) => valeur > 5
});

// Filtrer uniquement par clé
const parCle = filtrerMapAvance(monMap, {
  filtreCle: (cle) => cle.startsWith('a')
});

// Filtrer avec les deux
const parLesDeux = filtrerMapAvance(monMap, {
  filtreCle: (cle) => cle.length > 5,
  filtreValeur: (valeur) => valeur >= 3
});

// Filtrer avec une fonction complète
const avecFonction = filtrerMapAvance(monMap, {
  filtreComplet: (cle, valeur) => cle.includes('a') && valeur % 2 === 0
});

Filtrer un Map d'objets

Lorsque vous travaillez avec un Map contenant des objets comme valeurs, vous pouvez filtrer en fonction des propriétés de ces objets :

// Map avec des objets comme valeurs
const utilisateurs = new Map([
  ['user1', { nom: 'Alice', age: 30, actif: true }],
  ['user2', { nom: 'Bob', age: 25, actif: false }],
  ['user3', { nom: 'Charlie', age: 35, actif: true }],
  ['user4', { nom: 'Diana', age: 28, actif: true }]
]);

// Filtrer les utilisateurs actifs
const utilisateursActifs = new Map(
  [...utilisateurs.entries()].filter(([cle, valeur]) => valeur.actif)
);

console.log(utilisateursActifs);
// Map(3) { 'user1' => {...}, 'user3' => {...}, 'user4' => {...} }

// Filtrer les utilisateurs de plus de 30 ans
const utilisateursAges = new Map(
  [...utilisateurs.entries()].filter(([cle, valeur]) => valeur.age > 30)
);

// Filtrer les utilisateurs actifs ET de plus de 28 ans
const utilisateursFiltres = new Map(
  [...utilisateurs.entries()].filter(([cle, valeur]) => 
    valeur.actif && valeur.age > 28
  )
);

Cas d'usage pratiques

Scénario 1 : Filtrer des scores élevés

// Map de scores de joueurs
const scores = new Map([
  ['Alice', 95],
  ['Bob', 87],
  ['Charlie', 92],
  ['Diana', 88],
  ['Eve', 75]
]);

// Filtrer les joueurs avec plus de 85 points
const meilleursJoueurs = new Map(
  [...scores.entries()].filter(([nom, score]) => score > 85)
);

console.log(meilleursJoueurs);
// Map(4) { 'Alice' => 95, 'Bob' => 87, 'Charlie' => 92, 'Diana' => 88 }

Scénario 2 : Filtrer des produits en stock

const produits = new Map([
  ['laptop', { prix: 999, stock: 5 }],
  ['souris', { prix: 25, stock: 0 }],
  ['clavier', { prix: 75, stock: 12 }],
  ['écran', { prix: 200, stock: 3 }]
]);

// Filtrer les produits en stock (stock > 0)
const produitsDisponibles = new Map(
  [...produits.entries()].filter(([nom, produit]) => produit.stock > 0)
);

// Filtrer les produits en stock ET moins chers que 100€
const produitsAccessibles = new Map(
  [...produits.entries()].filter(([nom, produit]) => 
    produit.stock > 0 && produit.prix < 100
  )
);

Scénario 3 : Filtrer des données par date

const evenements = new Map([
  ['event1', { date: new Date('2024-01-15'), titre: 'Conférence' }],
  ['event2', { date: new Date('2024-02-20'), titre: 'Workshop' }],
  ['event3', { date: new Date('2024-03-10'), titre: 'Meetup' }],
  ['event4', { date: new Date('2024-04-05'), titre: 'Séminaire' }]
]);

const aujourdhui = new Date('2024-02-01');

// Filtrer les événements futurs
const evenementsFuturs = new Map(
  [...evenements.entries()].filter(([id, event]) => 
    event.date > aujourdhui
  )
);

console.log(evenementsFuturs);
// Map(3) { 'event2' => {...}, 'event3' => {...}, 'event4' => {...} }

Performance et bonnes pratiques

Le filtrage d'un Map a une complexité temporelle de O(n) où n est le nombre d'entrées. Voici quelques points à considérer :

  • Le filtrage crée un nouveau Map, donc il y a un coût en mémoire

  • Pour de très grandes collections, considérez d'utiliser une boucle for...of si vous n'avez besoin que d'itérer sur les résultats

  • Évitez les fonctions de filtrage trop complexes qui pourraient impacter les performances

  • Si vous devez filtrer fréquemment avec les mêmes critères, considérez de maintenir un Map filtré séparé

Chaînage de filtres

Vous pouvez chaîner plusieurs opérations de filtrage pour créer des filtres complexes :

// Chaîner plusieurs filtres
const resultat = new Map(
  [...monMap.entries()]
    .filter(([cle, valeur]) => valeur > 3)      // Premier filtre
    .filter(([cle, valeur]) => cle.length > 5) // Deuxième filtre
    .filter(([cle, valeur]) => cle.includes('a')) // Troisième filtre
);

// Alternative : combiner les conditions en un seul filtre (plus efficace)
const resultatOptimise = new Map(
  [...monMap.entries()].filter(([cle, valeur]) => 
    valeur > 3 && cle.length > 5 && cle.includes('a')
  )
);
Astuce : Combiner plusieurs conditions dans un seul filter() est généralement plus performant que de chaîner plusieurs appels à filter(), car cela évite de créer des tableaux intermédiaires.

Conclusion

Nous avons couvert les aspects essentiels du filtrage de Map en JavaScript. Bien que les Map n'aient pas de méthode filter() intégrée, il est facile de les filtrer en les convertissant en tableaux. La méthode la plus courante consiste à utiliser l'opérateur de décomposition [...map.entries()], appliquer filter() avec une fonction de prédicat, puis créer un nouveau Map à partir du tableau filtré.

Le filtrage fonctionne efficacement pour les valeurs simples, les objets complexes, et peut être combiné avec d'autres opérations comme le tri ou la transformation. N'hésitez pas à créer vos propres fonctions utilitaires pour réutiliser vos filtres dans différents contextes de votre application.