Comment compter des occurrences en PHP ?

Avec des if, puis sans if, puis array_key_exists, puis array_count_values et une belle classe pour finir

→ Challenge Correction: Team Pokemon

Qu’est ce que c’est une occurrence ?

Ce corrigé et ces explications s’appuient sur le challenge POKEMON_1.

Pour coller avec le thème, imagine que chaque Pokémon représente un élément dans une liste. Si tu te demandes combien de fois Pikachu apparaît, tu cherches en fait son « occurrence » dans cette liste.

En termes simples, une occurrence indique combien de fois un élément particulier se manifeste. En PHP, tu as plusieurs outils et techniques pour repérer et compter ces occurrences. En voici quelques uns :

V1, avec des if

$nbFeu = 0;
$nbEau = 0;
$nbHerbe = 0;
$nbRare = 0;

foreach ($types as $type) {

	if ($type === 'Feu') {
		$nbFeu++;
		continue;
	}

	if ($type === 'Eau') {
		$nbEau++;
		continue;
	}

	if ($type === 'Herbe') {
		$nbHerbe++;
		continue;
	}

	$nbRare++;
}

$reponse = min([$nbFeu, $nbEau, $nbHerbe, $nbRare]);

Explications

  • Je commence par initialiser 4 compteurs, tous à zéro, pour compter les types que je rencontre.
  • Je parcours ensuite chaque type. Dès que je trouve le type que je recherche, j’utilise un continue pour passer à l’itération suivante. Cela permet notamment ici d’éviter les if/elseif/elseif… successifs. Si je ne suis ni Feu, ni Eau, ni Herbe, c’est que je suis Rare (pas besoin de test supplémentaire donc ici).
  • J’utilise pour finir la fonction min pour retourner le minimum de chaque type/groupe qui sera le type/groupe discriminant pour construire mes groupes.

V2, plus de if, mais du typage

$nbFeu = 0;
$nbEau = 0;
$nbHerbe = 0;

foreach ($types as $type) {
	$nbFeu += (int) ($type === 'Feu');
	$nbEau += (int) ($type === 'Eau');
	$nbHerbe += (int) ($type === 'Herbe');
}

$nbRare = count($types) - $nbFeu - $nbEau - $nbHerbe;

$reponse = min([$nbFeu, $nbEau, $nbHerbe, $nbRare]);

Explications

  • Dans cette version, j’ai remplacé mes if par des incréments particuliers… En fait, je vais transformer le résultat de mon test en entier. Donc true => 1 et false => 0. De cette manière, si le test n’est pas vérifié, j’ajoute 0 (donc je ne fais rien). S’il est vérifié, j’ajoute 1.
  • Par contre, je ne peux pas utiliser la même chose pour compte les Rares. Du coup, je prends tous les types, moins ceux calculés dans le foreach.
  • Pas de changement pour l’utilisation de min.

Cette technique est décrite dans l’article dédié au remplacement des if dans le code en PHP.

V3, avec array_key_exists

$groupes = ['Feu' => 0, 'Eau' => 0, 'Herbe' => 0, 'Rare' => 0];

foreach ($types as $type) {
	if (array_key_exists($type, $groupes)) {
		$groupes[$type]++;
		continue;
	}

	$groupes['Rare']++;
}

echo min($groupes);

Explications

  • Cette fois-ci, au lieu d’initialiser 4 compteurs avec 4 variables, je crée un tableau qui contient mes compteurs. Mes clés portent les noms exacts des types « Feu », « Eau » et « Herbe ». Et une clé « Rare ».
  • Au parcours des types, je vais vérifier si le type courant a une clé correspondante dans $groupes. Si c’est le cas, j’incrémente le compteur correspondant de 1, et je passe à l’itération suivante avec le continue. Si ce n’est pas le cas, c’est que je suis dans le cas de « Rare » et c’est donc ce compteur que j’incrémente de 1.
  • Pour finir, il me suffit d’appliquer min directement sur $groupes. Ce sont les valeurs qui seront analysées, et non les clés. Donc peu importe comment les valeurs du tableau sont indexées.

V4, avec array_count_values

$values = array_count_values($types);

$reponse = min(
	$values['Feu'],
	$values['Eau'],
	$values['Herbe'],
	count($types) - $values['Feu'] - $values['Eau'] - $values['Herbe']
);

Explications

array_count_values est une fonction native de PHP, qui fait (grosso modo) exactement la même chose que la V3. Je vais récupérer un tableau avec comme clés les différents éléments de mon (premier) tableau, et comme valeurs le nombre d’occurrences de chacun de ces éléments.

ATTENTION : ce code n’est pas 100% correct ! En effet, si je n’ai pas de type « Eau », array_count_values ne saura pas créer « Eau » => 0. Il faudrait donc tester l’existence de chaque clé avant de vouloir l’utiliser…

V5, avec une fonction

function countType(string|array $searchType, array $types): int
{
	$searchType = (array) $searchType;

	$count = 0;
	foreach ($types as $type) {
		$count += (int) in_array($type, $searchType);
	}

	return $count;
}

$reponse = min(
	countType('Feu', $types),
	countType('Eau', $types),
	countType('Herbe', $types),
	countType(['Insecte', 'Psychique', 'Air', 'Glace', 'Poison'], $types)

Explications

  • La fonction countType permettra de compter le nombre d’occurrence d’un ou plusieurs types dans un tableau de type.
  • Cette ligne permet de transformer un élément seul en un tableau contenant ce seul élément. Un tableau sera inchangé.
  • Je parcours chaque type et mon compteur s’incrémente comme dans V2. J’utilise ici in_array, c’est pourquoi je tenais à avoir toujours pour $searchType.
  • Pour obtenir la réponse, j’applique donc ma fonction countType, toujours sur $types, mais en passant soit un type seul, soit un tableau de types recherchés, pour les rares.

Remarque : l’union de typage dans la déclaration d’une fonction, ici string|array, est disponible à partir de PHP 8.0

V6, avec une classe

final class POKEMONS
{
	private const TYPES_RARES = ['Air', 'Poison', 'Glace', 'Psychique', 'Insecte'];
	private const TYPES_CLASSIQUES = ['Feu', 'Eau', 'Herbe'];
	private array $types;
	private array $countTypes;

	public function __construct(array $types)
	{
		$this->types = $types;
		$this->countTypes = array_count_values($types);
	}

	private function countType(string $type): int
	{	
		return $this->countTypes[$type] ?? 0;
	}

	public function nbGroups(): int
	{
		return min(
			array_map([$this, 'countType'], self::TYPES_CLASSIQUES)
			+ 
			[array_sum(array_map([$this, 'countType'], self::TYPES_RARES))]
		);
	}
}

$reponse = (new POKEMONS($types))->nbGroups();

Explications

  • Je construis 2 constantes qui vont contenir la liste des types, selon qu’ils sont classiques ou rares.
  • À la construction de ma classe, j’applique tout de suite array_count_values sur $types.
  • Par contre, j’ai une méthode countType qui va vérifier que ma clé existe, et retournera 0 si elle n’existe pas. Comme ça, plus d’erreur comme expliqué dans V4. L’écriture « ?? » remplace « if(isset(…)) … ».Détails sur l’opérateur Null coalescent.
  • Enfin, dans nbGroups, je fais plusieurs choses…
  • Avec le premier array_map, je vais appliquer « countType » à chaque élément des types classiques. Je vais donc récupérer un tableau du genre [4, 3, 4] qui contient les occurrences de chaque type classique.
  • Pour les rares, j’applique par-dessus un array_sum pour faire la somme de tous les pokemons rares. Je n’aurais donc qu’une seule valeur, disons 5 pour l’exemple. Que je remets dans un tableau => [5].
  • Ensuite, le + me permet de faire [4, 3, 4] + [5]. Dans ce cas précis, cela me donne comme résultat [4, 3, 4, 5]. C’est sur ce tableau final que la fonction min est appliquée.

Conclusion

On a vu dans ce corrigé plusieurs façons de compter des occurrences en PHP. Certaines assez classiques, d’autres plus évoluées. Selon les problématiques que tu rencontreras, n’hésite pas à expérimenter avec de nouvelles idées et développer tes propres méthodes !

Tu peux aussi choisir un de ces challenges pour t’entrainer :


Qui a codé ce superbe contenu ?

Keep learning

Other content to discover