PHPackages                             andydefer/algokit - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. andydefer/algokit

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

andydefer/algokit
=================

Algo KIT - Probabilistic data structures and algorithms for large-scale data processing in PHP

v0.8.11(2w ago)026MITPHPPHP &gt;=8.1

Since Jun 30Pushed 2w agoCompare

[ Source](https://github.com/andydefer/algo-kit)[ Packagist](https://packagist.org/packages/andydefer/algokit)[ RSS](/packages/andydefer-algokit/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependencies (9)Versions (20)Used By (0)

AlgoKIT - Documentation complète
================================

[](#algokit---documentation-complète)

[![PHP Version](https://camo.githubusercontent.com/7535257ca228724c93658bd52583d4e47a9bab02c356abf6e54c1d575f2151e6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e312532422d626c75652e737667)](https://php.net)[![License](https://camo.githubusercontent.com/8bb50fd2278f18fc326bf71f6e88ca8f884f72f179d3e555e20ed30157190d0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

📖 Table des matières
--------------------

[](#-table-des-matières)

1. [Introduction](#introduction)
2. [Installation](#installation)
3. [Architecture](#architecture)
4. [Les structures de données](#les-structures-de-donn%C3%A9es)
    - [Trie - Autocomplétion](#trie---autocompl%C3%A9tion)
    - [BKTree - Correction orthographique](#bktree---correction-orthographique)
    - [BloomFilter - Test d'existence](#bloomfilter---test-dexistence)
    - [CountMinSketch - Comptage de fréquence](#countminsketch---comptage-de-fr%C3%A9quence)
    - [HyperLogLog - Comptage d'éléments uniques](#hyperloglog---comptage-d%C3%A9l%C3%A9ments-uniques)
    - [TopK - Éléments les plus fréquents](#topk---%C3%A9l%C3%A9ments-les-plus-fr%C3%A9quents)
    - [InvertedIndex - Index inversé](#invertedindex---index-invers%C3%A9)
5. [Cas d'usage réels](#cas-dusage-r%C3%A9els)
6. [Persistance](#persistance)
7. [Performance](#performance)
8. [API Reference](#api-reference)

---

Introduction
------------

[](#introduction)

**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.

---

Installation
------------

[](#installation)

```
composer require andydefer/algokit
```

### Prérequis

[](#prérequis)

- PHP 8.1 ou supérieur
- Extension `json` (optionnelle pour persistance)
- Extension `mbstring` (recommandée)

---

Architecture
------------

[](#architecture)

### StorageInterface

[](#storageinterface)

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 :**

- Changez de moteur de stockage sans modifier le code métier
- Testez facilement avec un storage en mémoire
- Partagez les données entre plusieurs instances
- Persistez automatiquement les données

**Storages disponibles :**

- `MemoryStorage` : Stockage en mémoire (tests, développement)
- `CacheStorage` : Stockage avec cache (Redis, Memcached)
- `JsonlStorage` : Stockage dans des fichiers JSONL

### Le concept de contexte

[](#le-concept-de-contexte)

Toutes 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']
```

---

Les structures de données
-------------------------

[](#les-structures-de-données)

### Trie - Autocomplétion

[](#trie---autocomplétion)

**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
```

### BKTree - Correction orthographique

[](#bktree---correction-orthographique)

**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)
```

### BloomFilter - Test d'existence

[](#bloomfilter---test-dexistence)

**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 :**

- ✅ **Pas de faux négatifs** : Si le test retourne `false`, l'élément n'est **certainement pas** dans l'ensemble
- ⚠️ **Faux positifs possibles** : Si le test retourne `true`, l'élément **probablement** dans l'ensemble
- La probabilité de faux positifs est contrôlée par la taille du filtre et le nombre de fonctions de hachage

**Utilisation 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";
}
```

### CountMinSketch - Comptage de fréquence

[](#countminsketch---comptage-de-fréquence)

**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 :**

- ✅ **Jamais de sous-estimation** : La valeur estimée est toujours ≥ à la valeur réelle
- ⚠️ **Surestimation possible** : Les collisions de hachage peuvent gonfler les compteurs
- L'erreur est bornée par `(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
```

### HyperLogLog - Comptage d'éléments uniques

[](#hyperloglog---comptage-déléments-uniques)

**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 :**

- L'algorithme observe la distribution des bits dans les hashs
- Plus il y a d'éléments uniques, plus il est probable d'observer des hashs avec de nombreux zéros initiaux
- La précision est contrôlée par le nombre de registres (2^precision)

**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
```

### TopK - Éléments les plus fréquents

[](#topk---éléments-les-plus-fréquents)

**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 :**

- La structure conserve exactement K éléments
- À chaque nouvel élément, si la liste est pleine, l'élément le moins fréquent est remplacé si le nouvel élément est plus fréquent
- Idéal pour les flux où on ne peut pas stocker tous les éléments

**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
```

### InvertedIndex - Index inversé

[](#invertedindex---index-inversé)

**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 :**

- ✅ **Recherche rapide** : O(1) par terme
- ✅ **Persistance** : Sauvegarde automatique via StorageInterface
- ✅ **Batch operations** : Ajout et recherche par lot
- ✅ **Statistiques** : Suivi des fréquences et de la taille de l'index
- ✅ **Suppression** : Suppression de documents ou de termes individuels

**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";
```

---

Cas d'usage réels
-----------------

[](#cas-dusage-réels)

### 1. Compteur d'IP uniques par jour

[](#1-compteur-dip-uniques-par-jour)

**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);
```

### 2. Système de recherche avec autocomplétion

[](#2-système-de-recherche-avec-autocomplétion)

**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";
}
```

### 3. Analyse de logs en temps réel

[](#3-analyse-de-logs-en-temps-réel)

**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";
```

### 4. Détection de spam

[](#4-détection-de-spam)

**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";
}
```

### 5. Système de recommandation

[](#5-système-de-recommandation)

**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";
}
```

---

Persistance
-----------

[](#persistance)

### Avec MemoryStorage (par défaut)

[](#avec-memorystorage-par-défaut)

```
$storage = new MemoryStorage();
$trie = new Trie($storage, 'my_trie');
// Les données sont stockées en mémoire
// Perdues à la fin du script
```

### Avec RedisStorage (exemple)

[](#avec-redisstorage-exemple)

```
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

Performance
-----------

[](#performance)

### Comparatif des structures

[](#comparatif-des-structures)

StructureInsertionRechercheMémoireIdéal pourTrieO(L)O(L + M)ÉlevéeAutocomplétionBKTreeO(log n)O(n)MoyenneCorrection orthographiqueBloomFilterO(k)O(k)Très faibleTest d'existenceCountMinSketchO(d)O(d)Très faibleComptage de fréquenceHyperLogLogO(1)O(m)Très faibleÉléments uniquesTopKO(k)O(k)FaibleTop fréquentsInvertedIndexO(n)O(1)MoyenneRecherche plein texte---

### Philosophie

[](#philosophie)

ProblèmeSolution classiqueSolution AlgoKITCompter 10M d'IP uniquesArray de 10M éléments (500MB)HyperLogLog (64KB)Suggestions en temps réelScanner tous les mots (lent)Trie (O(1) par caractère)Vérifier des URLs crawléesBase de données (lente)BloomFilter (O(k))Top 10 des recherchesTri de millions de logs (lourd)TopK (mémoire constante)Recherche plein texteScanner tous les documents (lent)InvertedIndex (O(1) par terme)### Les 7 structures clés

[](#les-7-structures-clés)

StructureRôleComplexitéCas d'usage**Trie**AutocomplétionO(L)Suggestions de recherche, dictionnaire**BKTree**Correction orthographiqueO(n × log n)"Vous avez voulu dire..."**BloomFilter**Test d'existenceO(k)URLs crawlées, cache bloqué**CountMinSketch**Comptage de fréquenceO(d)Analyse de logs, trending**HyperLogLog**Comptage d'éléments uniquesO(1)Visiteurs uniques, distincts**TopK**Éléments les plus fréquentsO(k)Classements, tendances**InvertedIndex**Index inverséO(1)Recherche plein texte, indexationLicense
-------

[](#license)

MIT © [Andy Defer](https://github.com/andydefer)

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance96

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

19

Last Release

19d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2170ec3fbad9eb4b002661ab4f58b1cc374eae4293b92904c6a74bc2818bd570?d=identicon)[andydefer](/maintainers/andydefer)

---

Top Contributors

[![andydefer](https://avatars.githubusercontent.com/u/124321745?v=4)](https://github.com/andydefer "andydefer (25 commits)")

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/andydefer-algokit/health.svg)

```
[![Health](https://phpackages.com/badges/andydefer-algokit/health.svg)](https://phpackages.com/packages/andydefer-algokit)
```

###  Alternatives

[nerds-and-company/schematic

Craft setup and sync tool

13655.2k1](/packages/nerds-and-company-schematic)[scandipwa/menu-organizer

Scandiweb Menu Organizer for ScandiPWA

13238.7k2](/packages/scandipwa-menu-organizer)[illuminatech/array-factory

Allows DI aware object creation from array definition

2168.7k6](/packages/illuminatech-array-factory)[yosimitso/workingforumbundle

A complete forum bundle

425.3k](/packages/yosimitso-workingforumbundle)[howdu/filament-record-switcher

Resource level navigation with search

1717.2k](/packages/howdu-filament-record-switcher)[vesselinv/fuel-sprockets

Asset management and asset bundling package for FuelPHP

166.2k](/packages/vesselinv-fuel-sprockets)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
