AlgoKIT est une bibliothèque PHP qui implémente des structures de données probabilistes et algorithmiques optimisées pour le traitement de données à grande échelle. Elle permet de résoudre des problèmes complexes (comptage de millions d'éléments, recherche en temps réel, analyse de flux) avec une consommation mémoire minimale.
composer require andydefer/algokit
json (optionnelle pour persistance)mbstring (recommandée)Toutes les structures utilisent une interface de stockage commune pour la persistance découplée.
interface StorageInterface
{
public function get(string $key, mixed $default = null): mixed;
public function set(string $key, mixed $value): void;
public function delete(string $key): bool;
public function exists(string $key): bool;
}
Avantages :
Storages disponibles :
MemoryStorage : Stockage en mémoire (tests, développement)CacheStorage : Stockage avec cache (Redis, Memcached)JsonlStorage : Stockage dans des fichiers JSONLToutes les structures supportent le contexte pour isoler les données par catégorie :
// Sans contexte → données globales
$trie->insert('laravel');
// Avec contexte → données isolées
$trie->insert('bonjour', 'french');
$trie->insert('hello', 'english');
// Recherche dans un contexte spécifique
$frenchWords = $trie->search('bon', 'french'); // ['bonjour']
$englishWords = $trie->search('hel', 'english'); // ['hello']
Description : Structure arborescente où chaque nœud représente un caractère. Les mots partageant un préfixe commun partagent le même chemin, permettant des recherches de préfixes en temps O(L) où L est la longueur du préfixe.
Théorie : Le Trie est idéal pour l'autocomplétion car il évite de scanner tous les mots à chaque recherche. La complexité ne dépend que de la longueur du préfixe, pas du nombre total de mots.
Utilisation typique : Autocomplétion de recherche, suggestions en temps réel, dictionnaire.
use AndyDefer\AlgoKIT\Algorithms\Trie;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
$storage = new MemoryStorage();
$trie = new Trie($storage, 'search_autocomplete');
// Indexation du dictionnaire
$words = ['laravel', 'laragon', 'large', 'laptop', 'php', 'python', 'pandas'];
foreach ($words as $word) {
$trie->insert($word);
}
// Recherche en temps réel
$query = 'la';
$suggestions = $trie->search($query, null, 5);
echo "Suggestions pour '$query' :\n";
foreach ($suggestions as $result) {
echo " • {$result->word}\n";
}
// Sortie :
// Suggestions pour 'la' :
// • laravel
// • laragon
// • large
// • laptop
// Avec contexte
$trie->insert('bonjour', 'french');
$trie->insert('bonsoir', 'french');
$trie->insert('hello', 'english');
$french = $trie->search('bon', 'french', 5);
foreach ($french as $result) {
echo "🇫🇷 {$result->word}\n";
}
// Sortie :
// 🇫🇷 bonjour
// 🇫🇷 bonsoir
Description : Arbre de Burkhard-Keller qui organise les mots par distance de Levenshtein. Permet de trouver les mots les plus proches d'une requête avec une tolérance configurable.
Théorie : La distance de Levenshtein mesure le nombre minimal de caractères à insérer, supprimer ou remplacer pour transformer un mot en un autre. Le BKTree explore seulement les branches de l'arbre susceptibles de contenir des mots dans la tolérance donnée.
Utilisation typique : Correction des fautes de frappe, suggestions "Vous avez voulu dire...".
use AndyDefer\AlgoKIT\Algorithms\BKTree;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
$storage = new MemoryStorage();
$bkTree = new BKTree($storage, 'dictionary');
// Indexation du dictionnaire
$words = ['php', 'python', 'laravel', 'javascript', 'symfony', 'golang'];
foreach ($words as $word) {
$bkTree->insert($word);
}
// Correction d'une faute de frappe
$typo = 'larvel';
$suggestions = $bkTree->search($typo, 2, 3);
echo "Suggestions pour '$typo' :\n";
foreach ($suggestions as $result) {
echo " • {$result->word} (distance: {$result->distance})\n";
}
// Sortie :
// Suggestions pour 'larvel' :
// • laravel (distance: 1)
// • javascript (distance: 6)
// • symfony (distance: 6)
// Tolérance plus élevée
$typo = 'javscrit';
$suggestions = $bkTree->search($typo, 3, 2);
foreach ($suggestions as $result) {
echo " • {$result->word} (distance: {$result->distance})\n";
}
// Sortie :
// • javascript (distance: 2)
// • php (distance: 7)
Description : Filtre probabiliste qui utilise un tableau de bits et plusieurs fonctions de hachage. L'insertion définit plusieurs bits à 1. Le test vérifie si tous les bits correspondants sont à 1.
Théorie :
false, l'élément n'est certainement pas dans l'ensembletrue, l'élément probablement dans l'ensembleUtilisation typique : Vérification d'URLs déjà crawlées, filtrage de spam, cache bloqué.
use AndyDefer\AlgoKIT\Algorithms\BloomFilter;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
$storage = new MemoryStorage();
$bloom = new BloomFilter($storage, 100000, 3, 'url_index');
// Indexation des URLs déjà crawlées
$urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
];
foreach ($urls as $url) {
$bloom->insert($url);
}
// Web crawler - éviter de recrawler
function shouldCrawl(BloomFilter $bloom, string $url): bool {
if ($bloom->exists($url)) {
echo "⏭️ $url déjà crawlée\n";
return false;
}
echo "🕷️ Crawl de $url\n";
$bloom->insert($url);
return true;
}
shouldCrawl($bloom, 'https://example.com/page1'); // ⏭️ déjà crawlée
shouldCrawl($bloom, 'https://example.com/page4'); // 🕷️ Crawl
shouldCrawl($bloom, 'https://example.com/page2'); // ⏭️ déjà crawlée
// Vérification par lot
use AndyDefer\AlgoKIT\Collections\BloomFilterCollection;
use AndyDefer\AlgoKIT\Records\BloomFilterRecord;
$collection = new BloomFilterCollection();
$collection->add(new BloomFilterRecord('https://example.com/page1'));
$collection->add(new BloomFilterRecord('https://example.com/page5'));
$results = $bloom->existsBatch($collection);
foreach ($results as $result) {
echo $result->value . " : " . ($result->exists ? '✅ existe' : '❌ inexistant') . "\n";
}
Description : Structure probabiliste utilisant une matrice de compteurs et plusieurs fonctions de hachage. Chaque insertion incrémente plusieurs compteurs. La fréquence estimée est le minimum des compteurs correspondants.
Théorie :
(width / 2) × depth avec une probabilité de 1 - e^(-depth)Utilisation typique : Analyse de logs, top des recherches, trending topics.
use AndyDefer\AlgoKIT\Algorithms\CountMinSketch;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
$storage = new MemoryStorage();
$cms = new CountMinSketch($storage, 10000, 5, 'search_frequencies');
// Tracker les recherches utilisateurs
$searches = [
'php', 'laravel', 'php', 'python', 'php',
'laravel', 'php', 'javascript', 'php', 'golang'
];
foreach ($searches as $term) {
$cms->add($term);
}
// Fréquences approximatives
echo "Fréquences approximatives :\n";
echo " php: " . $cms->count('php') . "\n"; // ~5
echo " laravel: " . $cms->count('laravel') . "\n"; // ~2
echo " python: " . $cms->count('python') . "\n"; // ~1
echo " ruby: " . $cms->count('ruby') . "\n"; // ~0
// Avec contexte (par site)
$cms->add('php', 'site_a');
$cms->add('php', 'site_a');
$cms->add('php', 'site_b');
echo "php sur site_a: " . $cms->count('php', 'site_a') . "\n"; // ~2
echo "php sur site_b: " . $cms->count('php', 'site_b') . "\n"; // ~1
// Opérations batch
use AndyDefer\AlgoKIT\Collections\CountMinSketchCollection;
use AndyDefer\AlgoKIT\Records\CountMinSketchRecord;
$batch = new CountMinSketchCollection();
$batch->add(new CountMinSketchRecord('php'));
$batch->add(new CountMinSketchRecord('php'));
$batch->add(new CountMinSketchRecord('laravel'));
$cms->addBatch($batch);
echo "php après batch: " . $cms->count('php') . "\n"; // ~7
Description : Algorithme qui estime le nombre d'éléments distincts en utilisant un tableau de registres. Chaque valeur est hachée, et le registre correspondant est mis à jour avec le nombre de zéros initiaux du hash.
Théorie :
Erreur standard : 1.04 / sqrt(2^precision)
precision = 10 (1024 registres) → erreur ~3.2%precision = 14 (16384 registres) → erreur ~0.8%precision = 16 (65536 registres) → erreur ~0.4%Utilisation typique : Visiteurs uniques, événements distincts, analyse de données massives.
use AndyDefer\AlgoKIT\Algorithms\HyperLogLog;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
$storage = new MemoryStorage();
$hll = new HyperLogLog($storage, 14, 'unique_visitors');
// Simuler un flux de visiteurs
$visitors = [
'user_123', 'user_456', 'user_123', 'user_789',
'user_456', 'user_321', 'user_123', 'user_654'
];
foreach ($visitors as $userId) {
$hll->add($userId);
}
$unique = $hll->count();
echo "Visiteurs uniques: $unique\n"; // ~5
// Par date (contexte)
$dates = ['2024-01-01', '2024-01-02'];
foreach ($visitors as $index => $userId) {
$date = $dates[$index % 2];
$hll->add($userId, $date);
}
echo "2024-01-01: " . $hll->count('2024-01-01') . " utilisateurs\n";
echo "2024-01-02: " . $hll->count('2024-01-02') . " utilisateurs\n";
echo "Total: " . $hll->count() . " utilisateurs\n";
// Opérations batch
use AndyDefer\AlgoKIT\Collections\HyperLogLogCollection;
use AndyDefer\AlgoKIT\Records\HyperLogLogRecord;
$batch = new HyperLogLogCollection();
$batch->add(new HyperLogLogRecord('user_999'));
$batch->add(new HyperLogLogRecord('user_888', '2024-01-03'));
$hll->addBatch($batch);
echo "Nouveau total: " . $hll->count() . "\n"; // ~6
Description : Structure qui maintient les K éléments les plus fréquents dans un flux. Utilise un espace constant et un algorithme de remplacement du moins fréquent.
Théorie :
Utilisation typique : Tendances, produits populaires, top des recherches.
use AndyDefer\AlgoKIT\Algorithms\TopK;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
$storage = new MemoryStorage();
$topK = new TopK($storage, 3, 'top_searches');
// Flux de recherches en temps réel
$searches = ['php', 'laravel', 'php', 'python', 'php', 'laravel', 'php', 'golang'];
foreach ($searches as $term) {
$topK->add($term);
}
// Top 3 des recherches
echo "Top recherches :\n";
foreach ($topK->getTop() as $rank => $item) {
echo " #" . ($rank + 1) . " {$item->value}: {$item->count}\n";
}
// Sortie :
// Top recherches :
// #1 php: 4
// #2 laravel: 2
// #3 python: 1
// Avec incréments plus importants
$topK->add('laravel', 5);
$topK->add('golang', 3);
echo "Après incréments :\n";
foreach ($topK->getTop() as $rank => $item) {
echo " #" . ($rank + 1) . " {$item->value}: {$item->count}\n";
}
// Sortie :
// Après incréments :
// #1 laravel: 7
// #2 php: 4
// #3 golang: 3
// Opérations batch
use AndyDefer\AlgoKIT\Collections\TopKCollection;
use AndyDefer\AlgoKIT\Records\TopKRecord;
$batch = new TopKCollection();
$batch->add(new TopKRecord('php', 10));
$batch->add(new TopKRecord('javascript', 2));
$topK->addBatch($batch);
echo "Final :\n";
foreach ($topK->getTop() as $rank => $item) {
echo " #" . ($rank + 1) . " {$item->value}: {$item->count}\n";
}
// Sortie :
// Final :
// #1 php: 14
// #2 laravel: 7
// #3 golang: 3
Description : Structure de données qui associe des termes (mots, n-grammes, ou tokens) aux documents qui les contiennent. Permet une recherche plein texte rapide et efficace.
Théorie : L'index inversé fonctionne comme une table de correspondance : pour chaque terme, on stocke la liste des documents où il apparaît. La recherche est alors un simple accès à cette table (O(1) par terme).
Propriétés fondamentales :
Utilisation typique : Moteurs de recherche, indexation de documents, systèmes de tagging.
use AndyDefer\AlgoKIT\Algorithms\InvertedIndex;
use AndyDefer\AlgoKIT\Records\InvertedIndexRecord;
use AndyDefer\AlgoKIT\Collections\InvertedIndexCollection;
use AndyDefer\StorageKit\Storage\MemoryStorage;
$storage = new MemoryStorage();
$index = new InvertedIndex($storage, 'document_index');
// Indexation de documents
$documents = [
['id' => 'doc_1', 'tokens' => ['php', 'laravel', 'framework']],
['id' => 'doc_2', 'tokens' => ['php', 'python', 'programming']],
['id' => 'doc_3', 'tokens' => ['laravel', 'vuejs', 'javascript']],
];
$collection = new InvertedIndexCollection();
foreach ($documents as $doc) {
$collection->add(InvertedIndexRecord::from([
'document_id' => $doc['id'],
'tokens' => $doc['tokens'],
]));
}
$index->addBatch($collection);
// Recherche simple
$results = $index->search('php');
echo "Documents contenant 'php' : " . implode(', ', $results->toArray()) . "\n";
// Sortie : doc_1, doc_2
// Recherche batch
use AndyDefer\AlgoKIT\Collections\InvertedIndexSearchCollection;
use AndyDefer\AlgoKIT\Records\InvertedIndexSearchRecord;
$search = new InvertedIndexSearchCollection();
$search->add(InvertedIndexSearchRecord::from(['token' => 'php']));
$search->add(InvertedIndexSearchRecord::from(['token' => 'laravel']));
$results = $index->searchBatch($search);
foreach ($results as $result) {
echo "'{$result->token}' → " . implode(', ', $result->document_ids->toArray()) . "\n";
}
// Sortie :
// 'php' → doc_1, doc_2
// 'laravel' → doc_1, doc_3
// Statistiques
$stats = $index->getStats();
echo "Tokens uniques : {$stats->total_tokens}\n";
echo "Entrées totales : {$stats->total_document_entries}\n";
echo "Fréquence max : {$stats->max_token_frequency}\n";
// Suppression
$index->remove('doc_1');
$stats = $index->getStats();
echo "Tokens après suppression : {$stats->total_tokens}\n";
Problème : Compter le nombre d'IP uniques qui visitent un site web chaque jour, avec des millions de requêtes.
Solution : HyperLogLog avec contexte temporel. 64KB de mémoire suffisent pour compter des milliards d'IP uniques.
use AndyDefer\AlgoKIT\Algorithms\HyperLogLog;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
use AndyDefer\AlgoKIT\Collections\HyperLogLogCollection;
use AndyDefer\AlgoKIT\Records\HyperLogLogRecord;
class UniqueIPCounter
{
private HyperLogLog $hll;
public function __construct(HyperLogLog $hll)
{
$this->hll = $hll;
}
public function trackVisit(string $ip): void
{
$date = date('Y-m-d');
$this->hll->add($ip, $date);
$this->hll->add($ip); // Global
}
public function getUniqueIPs(string $date): int
{
return $this->hll->count($date);
}
public function getTotalUniqueIPs(): int
{
return $this->hll->count();
}
public function getDailyStats(array $dates): array
{
$collection = new HyperLogLogCollection();
foreach ($dates as $date) {
$collection->add(new HyperLogLogRecord('dummy', $date));
}
$results = $this->hll->countBatch($collection);
$stats = [];
foreach ($results as $result) {
$stats[$result->context] = $result->count;
}
return $stats;
}
}
// ============================================
// UTILISATION
// ============================================
$storage = new MemoryStorage();
$hll = new HyperLogLog($storage, 14, 'ip_counter');
$counter = new UniqueIPCounter($hll);
// Simuler des visites sur 3 jours
$visits = [
'2024-01-01' => ['192.168.1.1', '192.168.1.2', '192.168.1.1', '192.168.1.3'],
'2024-01-02' => ['192.168.1.1', '192.168.1.4', '192.168.1.5', '192.168.1.1'],
'2024-01-03' => ['192.168.1.2', '192.168.1.3', '192.168.1.6']
];
foreach ($visits as $date => $ips) {
foreach ($ips as $ip) {
$counter->trackVisit($ip);
}
}
echo "=== Statistiques IP uniques ===\n";
echo "2024-01-01: " . $counter->getUniqueIPs('2024-01-01') . " IP uniques\n";
echo "2024-01-02: " . $counter->getUniqueIPs('2024-01-02') . " IP uniques\n";
echo "2024-01-03: " . $counter->getUniqueIPs('2024-01-03') . " IP uniques\n";
echo "Total: " . $counter->getTotalUniqueIPs() . " IP uniques\n";
$stats = $counter->getDailyStats(['2024-01-01', '2024-01-02']);
echo "Batch: " . print_r($stats, true);
Problème : Implémenter un système de recherche avec suggestions en temps réel et correction des fautes.
Solution : Combinaison de Trie (autocomplétion), BKTree (correction) et CountMinSketch (suivi des tendances).
use AndyDefer\AlgoKIT\Algorithms\Trie;
use AndyDefer\AlgoKIT\Algorithms\BKTree;
use AndyDefer\AlgoKIT\Algorithms\CountMinSketch;
use AndyDefer\AlgoKIT\Algorithms\TopK;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
class SearchEngine
{
private Trie $trie;
private BKTree $bkTree;
private CountMinSketch $cms;
private TopK $topK;
public function __construct(
Trie $trie,
BKTree $bkTree,
CountMinSketch $cms,
TopK $topK
) {
$this->trie = $trie;
$this->bkTree = $bkTree;
$this->cms = $cms;
$this->topK = $topK;
}
public function indexTerm(string $term): void
{
$this->trie->insert(strtolower($term));
$this->bkTree->insert(strtolower($term));
}
public function search(string $query, int $limit = 10): array
{
$query = strtolower(trim($query));
$results = [];
// 1. Autocomplétion (Trie)
if (strlen($query) > 0) {
$suggestions = $this->trie->search($query, null, $limit);
foreach ($suggestions as $suggestion) {
$results[] = [
'term' => $suggestion->word,
'type' => 'autocomplete',
'score' => $this->cms->count($suggestion->word) + 1
];
}
// 2. Correction orthographique (BKTree)
if (count($results) < $limit) {
$corrections = $this->bkTree->search($query, 2, $limit - count($results));
foreach ($corrections as $correction) {
$results[] = [
'term' => $correction->word,
'type' => 'correction',
'distance' => $correction->distance,
'score' => $this->cms->count($correction->word) + 1
];
}
}
}
// Tri par score décroissant
usort($results, fn($a, $b) => $b['score'] <=> $a['score']);
return array_slice($results, 0, $limit);
}
public function trackSearch(string $term): void
{
$term = strtolower(trim($term));
$this->cms->add($term);
$this->topK->add($term);
}
public function getPopularTerms(int $limit = 10): array
{
$top = $this->topK->getTop();
$result = [];
foreach ($top as $item) {
$result[] = [
'term' => $item->value,
'count' => $item->count
];
}
return array_slice($result, 0, $limit);
}
}
// ============================================
// UTILISATION
// ============================================
$storage = new MemoryStorage();
$searchEngine = new SearchEngine(
new Trie($storage, 'search_trie'),
new BKTree($storage, 'search_bktree'),
new CountMinSketch($storage, 10000, 5, 'search_cms'),
new TopK($storage, 10, 'search_topk')
);
// Indexation du dictionnaire
$terms = ['laravel', 'php', 'python', 'javascript', 'laragon', 'large', 'laptop'];
foreach ($terms as $term) {
$searchEngine->indexTerm($term);
}
// Simuler des recherches
$searches = ['php', 'laravel', 'php', 'python', 'php', 'laravel', 'php', 'laptop'];
foreach ($searches as $search) {
$searchEngine->trackSearch($search);
}
// Recherche avec autocomplétion
$query = 'la';
$results = $searchEngine->search($query, 5);
echo "Résultats pour '$query':\n";
foreach ($results as $result) {
echo "- {$result['term']} (type: {$result['type']}, score: {$result['score']})\n";
}
// Recherche avec correction
$typo = 'larvel';
$results = $searchEngine->search($typo, 5);
echo "\nRésultats pour '$typo':\n";
foreach ($results as $result) {
echo "- {$result['term']} (type: {$result['type']}, distance: " . ($result['distance'] ?? 'N/A') . ")\n";
}
// Top des recherches
$popular = $searchEngine->getPopularTerms(5);
echo "\nTop des recherches:\n";
foreach ($popular as $item) {
echo "- {$item['term']}: {$item['count']}\n";
}
Problème : Analyser les logs d'accès pour identifier les IPs les plus actives, les endpoints les plus sollicités, et les erreurs fréquentes.
Solution : Combinaison de CountMinSketch (fréquences), TopK (leaders) et HyperLogLog (IP uniques).
use AndyDefer\AlgoKIT\Algorithms\CountMinSketch;
use AndyDefer\AlgoKIT\Algorithms\TopK;
use AndyDefer\AlgoKIT\Algorithms\HyperLogLog;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
class LogAnalyzer
{
private CountMinSketch $cms;
private TopK $topK;
private HyperLogLog $hll;
public function __construct(
CountMinSketch $cms,
TopK $topK,
HyperLogLog $hll
) {
$this->cms = $cms;
$this->topK = $topK;
$this->hll = $hll;
}
public function processLog(array $log): void
{
$ip = $log['ip'] ?? 'unknown';
$endpoint = $log['endpoint'] ?? '/';
$status = $log['status'] ?? 200;
$date = date('Y-m-d');
// Fréquences
$this->cms->add("ip:{$ip}");
$this->cms->add("endpoint:{$endpoint}");
$this->cms->add("status:{$status}");
// Top K
$this->topK->add("ip:{$ip}");
$this->topK->add("endpoint:{$endpoint}");
$this->topK->add("status:{$status}");
// IP uniques par jour
$this->hll->add($ip, "daily_{$date}");
$this->hll->add($ip);
}
public function getTopIPs(int $limit = 10): array
{
$top = $this->topK->getTop();
$ips = [];
foreach ($top as $item) {
if (str_starts_with($item->value, 'ip:')) {
$ip = substr($item->value, 3);
$ips[] = [
'ip' => $ip,
'requests' => $item->count,
'estimated' => $this->cms->count("ip:{$ip}")
];
}
}
return array_slice($ips, 0, $limit);
}
public function getTopEndpoints(int $limit = 10): array
{
$top = $this->topK->getTop();
$endpoints = [];
foreach ($top as $item) {
if (str_starts_with($item->value, 'endpoint:')) {
$endpoint = substr($item->value, 9);
$endpoints[] = [
'endpoint' => $endpoint,
'requests' => $item->count,
'estimated' => $this->cms->count("endpoint:{$endpoint}")
];
}
}
return array_slice($endpoints, 0, $limit);
}
public function getTopStatuses(int $limit = 5): array
{
$top = $this->topK->getTop();
$statuses = [];
foreach ($top as $item) {
if (str_starts_with($item->value, 'status:')) {
$status = (int) substr($item->value, 7);
$statuses[] = [
'status' => $status,
'count' => $item->count
];
}
}
return array_slice($statuses, 0, $limit);
}
public function getUniqueIPsForDate(string $date): int
{
return $this->hll->count("daily_{$date}");
}
public function getTotalUniqueIPs(): int
{
return $this->hll->count();
}
}
// ============================================
// UTILISATION
// ============================================
$storage = new MemoryStorage();
$analyzer = new LogAnalyzer(
new CountMinSketch($storage, 50000, 5, 'log_cms'),
new TopK($storage, 20, 'log_topk'),
new HyperLogLog($storage, 14, 'log_hll')
);
// Simuler un flux de logs
$logs = [
['ip' => '192.168.1.1', 'endpoint' => '/api/users', 'status' => 200],
['ip' => '192.168.1.2', 'endpoint' => '/api/products', 'status' => 200],
['ip' => '192.168.1.1', 'endpoint' => '/api/users', 'status' => 200],
['ip' => '192.168.1.3', 'endpoint' => '/api/orders', 'status' => 404],
['ip' => '192.168.1.1', 'endpoint' => '/api/users', 'status' => 200],
['ip' => '192.168.1.2', 'endpoint' => '/api/products', 'status' => 200],
['ip' => '192.168.1.4', 'endpoint' => '/api/users', 'status' => 500],
['ip' => '192.168.1.1', 'endpoint' => '/api/orders', 'status' => 200],
];
foreach ($logs as $log) {
$analyzer->processLog($log);
}
echo "=== TOP IPS ===\n";
foreach ($analyzer->getTopIPs(5) as $ip) {
echo "- {$ip['ip']}: {$ip['requests']} requêtes\n";
}
echo "\n=== TOP ENDPOINTS ===\n";
foreach ($analyzer->getTopEndpoints(5) as $endpoint) {
echo "- {$endpoint['endpoint']}: {$endpoint['requests']} requêtes\n";
}
echo "\n=== TOP STATUS ===\n";
foreach ($analyzer->getTopStatuses() as $status) {
echo "- HTTP {$status['status']}: {$status['count']} occurrences\n";
}
echo "\n=== IP UNIQUES ===\n";
echo "Total: " . $analyzer->getTotalUniqueIPs() . " IP uniques\n";
echo "Aujourd'hui: " . $analyzer->getUniqueIPsForDate(date('Y-m-d')) . " IP uniques\n";
Problème : Détecter les messages de spam en vérifiant si le contenu a déjà été vu ou contient des mots-clés suspects.
Solution : BloomFilter pour la détection rapide avec contrôle des faux positifs.
use AndyDefer\AlgoKIT\Algorithms\BloomFilter;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
use AndyDefer\AlgoKIT\Collections\BloomFilterCollection;
use AndyDefer\AlgoKIT\Records\BloomFilterRecord;
class SpamDetector
{
private BloomFilter $bloom;
public function __construct(BloomFilter $bloom)
{
$this->bloom = $bloom;
}
public function markAsSpam(string $content): void
{
$hash = md5($content);
$this->bloom->insert($hash, 'spam');
$words = explode(' ', strtolower($content));
foreach ($words as $word) {
if (strlen($word) > 3) {
$this->bloom->insert($word, 'spam_keywords');
}
}
}
public function isSpam(string $content): bool
{
$hash = md5($content);
if ($this->bloom->exists($hash, 'spam')) {
return true;
}
$words = explode(' ', strtolower($content));
$spamScore = 0;
$totalWords = 0;
foreach ($words as $word) {
if (strlen($word) > 3) {
$totalWords++;
if ($this->bloom->exists($word, 'spam_keywords')) {
$spamScore++;
}
}
}
return $totalWords > 0 && ($spamScore / $totalWords) > 0.3;
}
public function isSpamBatch(array $messages): array
{
$collection = new BloomFilterCollection();
foreach ($messages as $id => $content) {
$hash = md5($content);
$collection->add(new BloomFilterRecord($hash, 'spam'));
}
$results = $this->bloom->existsBatch($collection);
$spam = [];
foreach ($results as $result) {
$spam[$result->value] = $result->exists;
}
return $spam;
}
}
// ============================================
// UTILISATION
// ============================================
$storage = new MemoryStorage();
$bloom = new BloomFilter($storage, 100000, 3, 'spam_filter');
$detector = new SpamDetector($bloom);
// Entraînement avec des spams connus
$spams = [
'Buy cheap viagra now!',
'Get rich quick!!!',
'FREE money making opportunity!',
'Click here for guaranteed results'
];
foreach ($spams as $spam) {
$detector->markAsSpam($spam);
}
// Tester des messages
$messages = [
'Buy cheap viagra now!',
'Hello, how are you today?',
'Get rich quick!!!',
'Meeting at 3pm tomorrow',
'FREE money making opportunity! Click here'
];
echo "=== DÉTECTION DE SPAM ===\n";
foreach ($messages as $message) {
$isSpam = $detector->isSpam($message);
echo ($isSpam ? "[SPAM] " : "[HAM] ") . $message . "\n";
}
Problème : Recommander des produits à un utilisateur en fonction de ses vues antérieures et de la popularité globale.
Solution : CountMinSketch (suivi des vues) + TopK (produits populaires) + filtrage par tags.
use AndyDefer\AlgoKIT\Algorithms\CountMinSketch;
use AndyDefer\AlgoKIT\Algorithms\TopK;
use AndyDefer\AlgoKIT\Storage\MemoryStorage;
use AndyDefer\AlgoKIT\Collections\CountMinSketchCollection;
use AndyDefer\AlgoKIT\Records\CountMinSketchRecord;
class RecommendationEngine
{
private CountMinSketch $cms;
private TopK $topK;
private array $productCatalog = [];
public function __construct(CountMinSketch $cms, TopK $topK)
{
$this->cms = $cms;
$this->topK = $topK;
}
public function addProduct(string $id, string $name, array $tags): void
{
$this->productCatalog[$id] = [
'name' => $name,
'tags' => $tags
];
}
public function trackView(string $userId, string $productId): void
{
$key = "{$userId}:{$productId}";
$this->cms->add($key);
$this->topK->add($key);
}
public function trackViewBatch(array $views): void
{
$collection = new CountMinSketchCollection();
foreach ($views as $view) {
$key = "{$view['user_id']}:{$view['product_id']}";
$collection->add(new CountMinSketchRecord($key));
}
$this->cms->addBatch($collection);
}
public function getUserPreferences(string $userId, int $limit = 5): array
{
$top = $this->topK->getTop();
$preferences = [];
foreach ($top as $item) {
if (str_starts_with($item->value, $userId . ':')) {
$productId = explode(':', $item->value)[1];
if (isset($this->productCatalog[$productId])) {
$preferences[] = [
'product_id' => $productId,
'product_name' => $this->productCatalog[$productId]['name'],
'views' => $item->count
];
}
if (count($preferences) >= $limit) {
break;
}
}
}
return $preferences;
}
public function getRecommendations(string $userId, int $limit = 5): array
{
$preferences = $this->getUserPreferences($userId, 3);
$tags = [];
foreach ($preferences as $pref) {
$tags = array_merge($tags, $this->productCatalog[$pref['product_id']]['tags']);
}
$tagCounts = array_count_values($tags);
arsort($tagCounts);
$topTags = array_slice(array_keys($tagCounts), 0, 3);
$recommendations = [];
foreach ($this->productCatalog as $id => $product) {
if (array_intersect($product['tags'], $topTags)) {
$recommendations[] = [
'product_id' => $id,
'product_name' => $product['name'],
'matching_tags' => array_intersect($product['tags'], $topTags)
];
}
if (count($recommendations) >= $limit) {
break;
}
}
return $recommendations;
}
public function getPopularProducts(int $limit = 10): array
{
$top = $this->topK->getTop();
$result = [];
foreach ($top as $item) {
$parts = explode(':', $item->value);
if (count($parts) === 2) {
$productId = $parts[1];
if (isset($this->productCatalog[$productId])) {
$result[] = [
'product_id' => $productId,
'product_name' => $this->productCatalog[$productId]['name'],
'views' => $item->count
];
}
}
if (count($result) >= $limit) {
break;
}
}
return $result;
}
}
// ============================================
// UTILISATION
// ============================================
$storage = new MemoryStorage();
$engine = new RecommendationEngine(
new CountMinSketch($storage, 10000, 5, 'rec_cms'),
new TopK($storage, 50, 'rec_topk')
);
// Catalogue produits
$products = [
['id' => 'p1', 'name' => 'Laptop', 'tags' => ['electronics', 'computer', 'gaming']],
['id' => 'p2', 'name' => 'Smartphone', 'tags' => ['electronics', 'mobile', 'communication']],
['id' => 'p3', 'name' => 'Headphones', 'tags' => ['electronics', 'audio', 'music']],
['id' => 'p4', 'name' => 'Book PHP', 'tags' => ['programming', 'php', 'web']],
['id' => 'p5', 'name' => 'Book Python', 'tags' => ['programming', 'python', 'ai']],
['id' => 'p6', 'name' => 'Tablet', 'tags' => ['electronics', 'mobile', 'reading']],
];
foreach ($products as $product) {
$engine->addProduct($product['id'], $product['name'], $product['tags']);
}
// Tracking des vues
$views = [
['user_id' => 'user_123', 'product_id' => 'p1'],
['user_id' => 'user_123', 'product_id' => 'p1'],
['user_id' => 'user_123', 'product_id' => 'p2'],
['user_id' => 'user_123', 'product_id' => 'p4'],
['user_id' => 'user_456', 'product_id' => 'p1'],
['user_id' => 'user_456', 'product_id' => 'p3'],
];
foreach ($views as $view) {
$engine->trackView($view['user_id'], $view['product_id']);
}
echo "=== PRÉFÉRENCES DE l'UTILISATEUR ===\n";
$preferences = $engine->getUserPreferences('user_123');
foreach ($preferences as $pref) {
echo "- {$pref['product_name']} (vues: {$pref['views']})\n";
}
echo "\n=== RECOMMANDATIONS ===\n";
$recommendations = $engine->getRecommendations('user_123', 5);
foreach ($recommendations as $rec) {
echo "- {$rec['product_name']} (tags: " . implode(', ', $rec['matching_tags']) . ")\n";
}
echo "\n=== PRODUITS POPULAIRES ===\n";
$popular = $engine->getPopularProducts(5);
foreach ($popular as $item) {
echo "- {$item['product_name']} (vues: {$item['views']})\n";
}
$storage = new MemoryStorage();
$trie = new Trie($storage, 'my_trie');
// Les données sont stockées en mémoire
// Perdues à la fin du script
class RedisStorage implements StorageInterface
{
private \Redis $redis;
public function __construct(\Redis $redis)
{
$this->redis = $redis;
}
public function get(string $key, mixed $default = null): mixed
{
$value = $this->redis->get($key);
return $value !== false ? unserialize($value) : $default;
}
public function set(string $key, mixed $value): void
{
$this->redis->set($key, serialize($value));
}
public function delete(string $key): bool
{
return (bool) $this->redis->del($key);
}
public function exists(string $key): bool
{
return (bool) $this->redis->exists($key);
}
}
// Utilisation
$redis = new \Redis();
$redis->connect('localhost', 6379);
$storage = new RedisStorage($redis);
$trie = new Trie($storage, 'persistent_trie');
// Les données sont persistées dans Redis
---a
| Structure | Insertion | Recherche | Mémoire | Idéal pour |
|---|---|---|---|---|
| Trie | O(L) | O(L + M) | Élevée | Autocomplétion |
| BKTree | O(log n) | O(n) | Moyenne | Correction orthographique |
| BloomFilter | O(k) | O(k) | Très faible | Test d'existence |
| CountMinSketch | O(d) | O(d) | Très faible | Comptage de fréquence |
| HyperLogLog | O(1) | O(m) | Très faible | Éléments uniques |
| TopK | O(k) | O(k) | Faible | Top fréquents |
| InvertedIndex | O(n) | O(1) | Moyenne | Recherche plein texte |
| Problème | Solution classique | Solution AlgoKIT |
|---|---|---|
| Compter 10M d'IP uniques | Array de 10M éléments (500MB) | HyperLogLog (64KB) |
| Suggestions en temps réel | Scanner tous les mots (lent) | Trie (O(1) par caractère) |
| Vérifier des URLs crawlées | Base de données (lente) | BloomFilter (O(k)) |
| Top 10 des recherches | Tri de millions de logs (lourd) | TopK (mémoire constante) |
| Recherche plein texte | Scanner tous les documents (lent) | InvertedIndex (O(1) par terme) |
| Structure | Rôle | Complexité | Cas d'usage |
|---|---|---|---|
| Trie | Autocomplétion | O(L) | Suggestions de recherche, dictionnaire |
| BKTree | Correction orthographique | O(n × log n) | "Vous avez voulu dire..." |
| BloomFilter | Test d'existence | O(k) | URLs crawlées, cache bloqué |
| CountMinSketch | Comptage de fréquence | O(d) | Analyse de logs, trending |
| HyperLogLog | Comptage d'éléments uniques | O(1) | Visiteurs uniques, distincts |
| TopK | Éléments les plus fréquents | O(k) | Classements, tendances |
| InvertedIndex | Index inversé | O(1) | Recherche plein texte, indexation |
MIT © Andy Defer
How can I help you explore Laravel packages today?