<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
	<id>http://os-vps418.infomaniak.ch:1250/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Tphilippe</id>
	<title>Wiki du LAMA (UMR 5127) - Contributions [fr]</title>
	<link rel="self" type="application/atom+xml" href="http://os-vps418.infomaniak.ch:1250/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Tphilippe"/>
	<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php/Sp%C3%A9cial:Contributions/Tphilippe"/>
	<updated>2026-05-21T08:01:08Z</updated>
	<subtitle>Contributions</subtitle>
	<generator>MediaWiki 1.39.4</generator>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15622</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15622"/>
		<updated>2024-05-23T16:39:21Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;exécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
Ce code à été réalisé en POO (programmation orienté objet) qui utilise donc ce que l&#039;on appelle des &amp;quot;classes&amp;quot; qui regroupe des fonctions qu&#039;on appelle méthodes. Le code suivant à été simplifié et abrégé. Le réel code est disponible sur github (voir la section lien).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
                 &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et une première liste E qui contient des elements inéxistants mais qui pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    update(k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         Si l&#039;élément est présent:&lt;br /&gt;
                  replace_min(k)&lt;br /&gt;
         incr(k)&lt;br /&gt;
&lt;br /&gt;
    replace_min(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    incr(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On conserve la complexité de mémoire avantageuses de Stream Summary. En ce qui concerne la complexité de vitesse, on à de nouveau une complexité linéaire puisque le nombre d&#039;opérations est borné. &lt;br /&gt;
&lt;br /&gt;
Nous sommes donc parvenu à résoudre le problème de mémoire grâce à Stream Summary !&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes (attention, les résultats pour l&#039;algorithme &amp;quot;élément majoritaire dictionnaire&amp;quot; ne sont pas significatif puisqu&#039;il ne fonctionne qu&#039;avec 2 compteurs, même lorsque l&#039;on fait varier le nombre de compteurs) :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15621</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15621"/>
		<updated>2024-05-23T16:39:07Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;exécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
Ce code à été réalisé en POO (programmation orienté objet) qui utilise donc ce que l&#039;on appelle des &amp;quot;classes&amp;quot; qui regroupe des fonctions qu&#039;on appelle méthodes. Le code suivant à été simplifié et abrégé. Le réel code est disponible sur github (voir la section lien).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
                 &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et une première liste E qui contient des elements inéxistants mais qui pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    update(k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         Si l&#039;élément est présent:&lt;br /&gt;
                  replace_min(k)&lt;br /&gt;
         incr(k)&lt;br /&gt;
&lt;br /&gt;
    replace_min(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    incr(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On conserve la complexité de mémoire avantageuses de Stream Summary. En ce qui concerne la complexité de vitesse, on à de nouveau une complexité linéaire puisque le nombre d&#039;opérations est borné. &lt;br /&gt;
&lt;br /&gt;
Nous sommes donc parvenu à résoudre le problème de mémoire grâce à Stream Summary !&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes (attention, les résultats pour l&#039;algorithme &amp;quot;élément majoritaire certain&amp;quot; ne sont pas significatif puisqu&#039;il ne fonctionne qu&#039;avec 2 compteurs, même lorsque l&#039;on fait varier le nombre de compteurs) :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15620</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15620"/>
		<updated>2024-05-23T16:37:00Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;exécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
Ce code à été réalisé en POO (programmation orienté objet) qui utilise donc ce que l&#039;on appelle des &amp;quot;classes&amp;quot; qui regroupe des fonctions qu&#039;on appelle méthodes. Le code suivant à été simplifié et abrégé. Le réel code est disponible sur github (voir la section lien).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
                 &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et une première liste E qui contient des elements inéxistants mais qui pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    update(k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         Si l&#039;élément est présent:&lt;br /&gt;
                  replace_min(k)&lt;br /&gt;
         incr(k)&lt;br /&gt;
&lt;br /&gt;
    replace_min(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    incr(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On conserve la complexité de mémoire avantageuses de Stream Summary. En ce qui concerne la complexité de vitesse, on à de nouveau une complexité linéaire puisque le nombre d&#039;opérations est borné. &lt;br /&gt;
&lt;br /&gt;
Nous sommes donc parvenu à résoudre le problème de mémoire grâce à Stream Summary !&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15619</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15619"/>
		<updated>2024-05-23T16:35:55Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
Ce code à été réalisé en POO (programmation orienté objet) qui utilise donc ce que l&#039;on appelle des &amp;quot;classes&amp;quot; qui regroupe des fonctions qu&#039;on appelle méthodes. Le code suivant à été simplifié et abrégé. Le réel code est disponible sur github (voir la section lien).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
                 &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et une première liste E qui contient des elements inéxistants mais qui pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    update(k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         Si l&#039;élément est présent:&lt;br /&gt;
                  replace_min(k)&lt;br /&gt;
         incr(k)&lt;br /&gt;
&lt;br /&gt;
    replace_min(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    incr(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On conserve la complexité de mémoire avantageuses de Stream Summary. En ce qui concerne la complexité de vitesse, on à de nouveau une complexité linéaire puisque le nombre d&#039;opérations est borné. &lt;br /&gt;
&lt;br /&gt;
Nous sommes donc parvenu à résoudre le problème de mémoire grâce à Stream Summary !&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15618</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15618"/>
		<updated>2024-05-23T16:34:03Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
Ce code à été réalisé en POO (programmation orienté objet) qui utilise donc ce que l&#039;on appelle des &amp;quot;classes&amp;quot; qui regroupe des fonctions qu&#039;on appelle méthodes. Le code suivant à été simplifié et abrégé. Le réel code est disponible sur github (voir la section lien).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
                 &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et une première liste E qui contient des elements inéxistants mais qui pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    update(k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         Si l&#039;élément est présent:&lt;br /&gt;
                  replace_min(k)&lt;br /&gt;
         incr(k)&lt;br /&gt;
&lt;br /&gt;
    replace_min(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    incr(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15617</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15617"/>
		<updated>2024-05-23T16:33:42Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
Ce code à été réalisé en POO (programmation orienté objet) qui utilise donc ce que l&#039;on appelle des &amp;quot;classes&amp;quot; qui regroupe des fonctions qu&#039;on appelle méthodes. Le code suivant à été simplifié et abrégé. Le réel code est disponible sur github (voir la section lien).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et une première liste E qui contient des elements inéxistants mais qui pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    update(k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         Si l&#039;élément est présent:&lt;br /&gt;
                  replace_min(k)&lt;br /&gt;
         incr(k)&lt;br /&gt;
&lt;br /&gt;
    replace_min(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    incr(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15616</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15616"/>
		<updated>2024-05-23T16:33:02Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
Ce code à été réalisé en POO (programmation orienté objet) qui utilise donc ce que l&#039;on appelle des &amp;quot;classes&amp;quot; qui regroupe des fonctions qu&#039;on appelle méthodes. Le code suivant à été simplifié et abrégé. Le réel code est disponible sur github (voir la section lien).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et une première liste E qui contient des elements inéxistants mais qui pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    update(k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         Si l&#039;élément est présent:&lt;br /&gt;
                  replace_min(k)&lt;br /&gt;
         incr(k)&lt;br /&gt;
&lt;br /&gt;
    replace_min(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    incr(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15615</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15615"/>
		<updated>2024-05-23T16:31:46Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
Ce code à été réalisé en POO (programmation orienté objet) qui utilise donc ce que l&#039;on appelle des &amp;quot;classes&amp;quot; qui regroupe des fonctions qu&#039;on appelle méthodes. Le code suivant à été simplifié et abrégé. Le réel code est disponible sur github (voir la section lien).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et une première liste E qui contient des elements inéxistants mais qui pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    update(k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    replace_min(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    incr(k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15614</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15614"/>
		<updated>2024-05-23T16:28:31Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
&lt;br /&gt;
     add:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     pop:&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         E = Element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     init:&lt;br /&gt;
         value = Valeur&lt;br /&gt;
         C = Compteur&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    init: &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15613</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15613"/>
		<updated>2024-05-23T16:26:26Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La distribution Zipf */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
On dois au moins retenir de cette distribution que certains éléments apparaissent beaucoup plus que d&#039;autres. Ainsi, lors des tests sur les résultats pour Zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15612</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15612"/>
		<updated>2024-05-23T16:25:08Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* L&amp;#039;algorithme, principe et propriétés */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On ne conserve pas tout les éléments. On à donc d&#039;abord une complexité de mémoire de 0(k) avec k le nombre de compteurs. C&#039;est un gain extrême en comparaison de notre premier algorithme. Là où pour 10 millions d&#039;éléments le précédant prenait 10 millions d&#039;emplacements, on à ici que l&#039;on en à besoin que de 2.&lt;br /&gt;
&lt;br /&gt;
En ce qui concerne la vitesse cependant, on à ici une complexité de 0(n*k). Cela s&#039;explique. A chaque nouvel éléments on doit d&#039;abord parcourir la liste de tout les compteurs avant de l&#039;incrémenter ou d&#039;effectuer une opération de remplacement.&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
Ainsi, lors des tests sur les résultats pour zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15611</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15611"/>
		<updated>2024-05-23T16:24:15Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour n éléments, il s&#039;exécutera en temps n. À ce jour, il n&#039;existe pas d&#039;algorithme plus rapide en termes de complexité de vitesse pour cette tâche. On peut améliorer les performances, mais la complexité restera au moins O(n).&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème. Cet algorithme n&#039;est donc pas adapté face à de grands volumes de données.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
Ainsi, lors des tests sur les résultats pour zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15607</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15607"/>
		<updated>2024-05-22T01:10:18Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La distribution Zipf */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
La distribution de Zipf est une loi de probabilité selon laquelle la fréquence d’un événement est inversement proportionnelle à son rang. En d&#039;autres termes, dans une distribution de Zipf, le premier élément le plus fréquent apparaît environ deux fois plus souvent que le deuxième élément le plus fréquent, trois fois plus souvent que le troisième, et ainsi de suite. Cette distribution est fréquemment observée dans des phénomènes naturels et sociaux, comme la fréquence des mots dans une langue, la population des villes, et les requêtes sur les moteurs de recherche.&lt;br /&gt;
&lt;br /&gt;
Ainsi, lors des tests sur les résultats pour zipf, l&#039;algorithme était parfaitement adapté.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15606</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15606"/>
		<updated>2024-05-22T01:09:25Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;exécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé 5 graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous voyons que le temps dépend uniquement pour l&#039;algorithme Stream Summary du nombre d&#039;éléments, de même que pour élément majoritaire dictionnaire.&lt;br /&gt;
Les résultat sont donc bien ceux qu&#039;on attend.&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Lorsqu&#039;on fait cette fois monter le nombre d&#039;éléments, on observe bien que la complexité est linéaire, ou en tout cas qu&#039;elle dépend bien de n.&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
Ici nous faisons de nouveau varier le nombre de compteurs. Cependant, même avec beaucoup d&#039;éléments, l&#039;algorithme Space Saving avec les dictionnaire est plus rapide. Ici nous utilisons des générateurs, mon hypothèse est que le générateur est plus long pour envoyer des éléments, et que ceux-ci sont traités plus rapidement par Space Saving dictionnaire que par Stream Summary. Si tout les éléments étaient envoyés d&#039;un coup, je pense que Stream Summary aurait été plus efficace.&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15605</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15605"/>
		<updated>2024-05-22T01:01:55Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;exécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|500px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|500px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|500px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|500px|]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15604</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15604"/>
		<updated>2024-05-22T01:01:17Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|400px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|400px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|400px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|400px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|400px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|400px|]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15603</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15603"/>
		<updated>2024-05-22T01:00:40Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|300px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|300px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|300px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|300px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|300px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|300px|]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15602</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15602"/>
		<updated>2024-05-22T01:00:22Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|thumb|300px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|300px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|thumb|300px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15601</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15601"/>
		<updated>2024-05-22T00:59:42Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|thumb|300px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|thumb|300px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|thumb|300px|]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15600</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15600"/>
		<updated>2024-05-22T00:59:21Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:lst1_Xpge_100elem.jpg|thumb|300px|]]&lt;br /&gt;
[[File:lst1_Xpge_10000elem.jpg|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:lst2_10pge_Xelem.jpg|thumb|300px|]]&lt;br /&gt;
[[File:lst2_100pge_Xelem.jpg|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|thumb|300px|gene2_Xpge_10000elem]]&lt;br /&gt;
[[File:gene2_Xpge_10000elem_zoom.jpg|thumb|300px|gene2_Xpge_10000elem]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15599</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15599"/>
		<updated>2024-05-22T00:57:21Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem.jpg|thumb|300px|gene2_Xpge_10000elem]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15598</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15598"/>
		<updated>2024-05-22T00:56:31Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem|thumb|300px|gene2_Xpge_10000elem]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15597</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15597"/>
		<updated>2024-05-22T00:56:08Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Comparatifs de temps d&amp;#039;éxécutions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
[[File:gene2_Xpge_10000elem|thumb|300px|]]&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Lst2_100pge_Xelem.jpg&amp;diff=15596</id>
		<title>Fichier:Lst2 100pge Xelem.jpg</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Lst2_100pge_Xelem.jpg&amp;diff=15596"/>
		<updated>2024-05-22T00:55:11Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Lst2_10pge_Xelem.jpg&amp;diff=15595</id>
		<title>Fichier:Lst2 10pge Xelem.jpg</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Lst2_10pge_Xelem.jpg&amp;diff=15595"/>
		<updated>2024-05-22T00:55:04Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Lst1_Xpge_10000elem.jpg&amp;diff=15594</id>
		<title>Fichier:Lst1 Xpge 10000elem.jpg</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Lst1_Xpge_10000elem.jpg&amp;diff=15594"/>
		<updated>2024-05-22T00:54:54Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Lst1_Xpge_100elem.jpg&amp;diff=15593</id>
		<title>Fichier:Lst1 Xpge 100elem.jpg</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Lst1_Xpge_100elem.jpg&amp;diff=15593"/>
		<updated>2024-05-22T00:54:43Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Gene2_Xpge_10000elem_zoom.jpg&amp;diff=15592</id>
		<title>Fichier:Gene2 Xpge 10000elem zoom.jpg</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Gene2_Xpge_10000elem_zoom.jpg&amp;diff=15592"/>
		<updated>2024-05-22T00:54:33Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Gene2_Xpge_10000elem.jpg&amp;diff=15591</id>
		<title>Fichier:Gene2 Xpge 10000elem.jpg</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Gene2_Xpge_10000elem.jpg&amp;diff=15591"/>
		<updated>2024-05-22T00:54:06Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15408</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15408"/>
		<updated>2024-05-16T13:44:38Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15407</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15407"/>
		<updated>2024-05-16T13:39:42Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     ...&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15406</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15406"/>
		<updated>2024-05-16T13:38:03Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; &lt;br /&gt;
     mais d&#039;un chaine d&#039;éléments liés entre eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15405</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15405"/>
		<updated>2024-05-16T05:44:13Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; &lt;br /&gt;
     mais d&#039;un chaine d&#039;éléments liés entre eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15404</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15404"/>
		<updated>2024-05-14T22:38:08Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; &lt;br /&gt;
     mais d&#039;un chaine d&#039;éléments liés entre eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15403</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15403"/>
		<updated>2024-05-14T22:37:37Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
//**def space_saving(L, k)://**&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; &lt;br /&gt;
     mais d&#039;un chaine d&#039;éléments liés entre eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15402</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15402"/>
		<updated>2024-05-14T22:37:26Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
**def space_saving(L, k):**&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; &lt;br /&gt;
     mais d&#039;un chaine d&#039;éléments liés entre eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15401</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15401"/>
		<updated>2024-05-14T22:36:13Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; &lt;br /&gt;
     mais d&#039;un chaine d&#039;éléments liés entre eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste par celui ci en conservant le compteur, &lt;br /&gt;
         conformément au code space saving. On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprime l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            ...&lt;br /&gt;
        else: # Le deuxième cas : on doit crée ce compteur avec la valeur + 1&lt;br /&gt;
            ...&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15400</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15400"/>
		<updated>2024-05-14T22:33:21Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; &lt;br /&gt;
     mais d&#039;un chaine d&#039;éléments liés entre eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste&lt;br /&gt;
         par celui ci en conservant le compteur, conformément au code space saving.&lt;br /&gt;
         On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprimer l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            C.next.E.add(E) # On ajoute l&#039;élément dans la liste d&#039;éléments du compteur suivant&lt;br /&gt;
            E.C = C.next # On définit le pointeur de cet élément sur le compteur suivant&lt;br /&gt;
        else:&lt;br /&gt;
            nC = Counter(C.value+1, E)&lt;br /&gt;
            C.add(nC) # On ajoute un nouveau compteur qui à la valeur +1 dans la liste des compteurs, et on définit sa liste d&#039;éléments avec notre Element&lt;br /&gt;
            E.C = nC # On redéfinit ici aussi le pointeur de notre élément sur ce compteur&lt;br /&gt;
            &lt;br /&gt;
            #Puisque l&#039;élément est dans seul dans une liste et qu&#039;il à conservé ses pointeurs, on les rédinit sur lui même&lt;br /&gt;
            E.prev = E &lt;br /&gt;
            E.next = E&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15399</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15399"/>
		<updated>2024-05-14T22:33:01Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting&lt;br /&gt;
     cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; &lt;br /&gt;
     mais d&#039;un chaine d&#039;éléments liés entre eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste&lt;br /&gt;
         par celui ci en conservant le compteur, conformément au code space saving.&lt;br /&gt;
         On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprimer l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            C.next.E.add(E) # On ajoute l&#039;élément dans la liste d&#039;éléments du compteur suivant&lt;br /&gt;
            E.C = C.next # On définit le pointeur de cet élément sur le compteur suivant&lt;br /&gt;
        else:&lt;br /&gt;
            nC = Counter(C.value+1, E)&lt;br /&gt;
            C.add(nC) # On ajoute un nouveau compteur qui à la valeur +1 dans la liste des compteurs, et on définit sa liste d&#039;éléments avec notre Element&lt;br /&gt;
            E.C = nC # On redéfinit ici aussi le pointeur de notre élément sur ce compteur&lt;br /&gt;
            &lt;br /&gt;
            #Puisque l&#039;élément est dans seul dans une liste et qu&#039;il à conservé ses pointeurs, on les rédinit sur lui même&lt;br /&gt;
            E.prev = E &lt;br /&gt;
            E.next = E&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15398</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15398"/>
		<updated>2024-05-14T22:32:31Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* La structure de donnée Stream Summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting&lt;br /&gt;
     cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; mais d&#039;un chaine d&#039;éléments liés entre&lt;br /&gt;
     eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste&lt;br /&gt;
         par celui ci en conservant le compteur, conformément au code space saving.&lt;br /&gt;
         On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprimer l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            C.next.E.add(E) # On ajoute l&#039;élément dans la liste d&#039;éléments du compteur suivant&lt;br /&gt;
            E.C = C.next # On définit le pointeur de cet élément sur le compteur suivant&lt;br /&gt;
        else:&lt;br /&gt;
            nC = Counter(C.value+1, E)&lt;br /&gt;
            C.add(nC) # On ajoute un nouveau compteur qui à la valeur +1 dans la liste des compteurs, et on définit sa liste d&#039;éléments avec notre Element&lt;br /&gt;
            E.C = nC # On redéfinit ici aussi le pointeur de notre élément sur ce compteur&lt;br /&gt;
            &lt;br /&gt;
            #Puisque l&#039;élément est dans seul dans une liste et qu&#039;il à conservé ses pointeurs, on les rédinit sur lui même&lt;br /&gt;
            E.prev = E &lt;br /&gt;
            E.next = E&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15397</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15397"/>
		<updated>2024-05-14T22:31:24Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def space_saving(L, k):&lt;br /&gt;
     S = StreamSummary(k) # On initialise un objet S de Stream Summary&lt;br /&gt;
     for e in L:     # Nous allons prendre un à un les éléments de L &lt;br /&gt;
         S.update(e) # Et les ajouté avec une méthode de Stream Summary&lt;br /&gt;
     return S.list() # On renvoie la listes des éléments majoritaires avec la méthode &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Cell:&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;non-empty doubly linked circular lists, identified by a single starting&lt;br /&gt;
     cell&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, v): # On définit une cellule comme ayant&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;create a doubly linked circular list with a single value&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         self.prev = self   # Un pointeur vers l&#039;élément précédent (qui au début est lui-même)&lt;br /&gt;
         self.next = self   # Un pointeur vers l&#039;élément suivant (qui au début est lui-même)&lt;br /&gt;
         self.value = v     # Une valeur v&lt;br /&gt;
&lt;br /&gt;
     def add(self, c):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On ajoute l&#039;élément dans une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui partent vers l&#039;éléments et ceux qui pointent vers celui-ci&amp;quot;&amp;quot;&amp;quot;      &lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
               &lt;br /&gt;
     def pop(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;On enlève l&#039;élément d&#039;une liste doublement chainée&lt;br /&gt;
         Redéfinit les pointeurs qui pointaient vers l&#039;élément&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         ...&lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
class Counter(Cell):&lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un compteur est une cellule qui pointe vers une &amp;quot;liste&amp;quot; d&#039;éléments (Attention, il ne s&#039;agit pas de la structure de donnée &amp;quot;liste&amp;quot; mais d&#039;un chaine d&#039;éléments liés entre &lt;br /&gt;
     eux par des pointeurs).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, c, E=None):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = c          # actual value of the counter (int)&lt;br /&gt;
         self.E = E              # reference to list of elements sharing this counter&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Element(Cell): &lt;br /&gt;
     &amp;quot;&amp;quot;&amp;quot;Un élément est une cellule qui pointe vers un compteur.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
     def __init__(self, e, C):&lt;br /&gt;
         self.prev = self&lt;br /&gt;
         self.next = self&lt;br /&gt;
         self.value = e          # actual value of the element&lt;br /&gt;
         self.C = C              # reference to counter for this element&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class StreamSummary:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;C&#039;est la classe principale qui est chargée d&#039;appeler de gérer et de redéfinir les pointeurs à chaque itérations&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, k): &lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            On initialise la liste des compteurs, le dictionnaires d&#039;éléments et &lt;br /&gt;
            une première liste E qui contient des elements inéxistants mais qui &lt;br /&gt;
            pointent vers le compteur 0&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
    def list(self):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot; Renvoi la liste des éléments majoritaires à partir du dictionnaire &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         liste = []&lt;br /&gt;
         for i, j in self.elements.items():&lt;br /&gt;
             liste.append([i,j.C.value])&lt;br /&gt;
         return liste&lt;br /&gt;
&lt;br /&gt;
    def update(self, k):&lt;br /&gt;
         &amp;quot;&amp;quot;&amp;quot;Ici, si l&#039;élément k n&#039;existe pas, on remplace un element de la liste&lt;br /&gt;
         par celui ci en conservant le compteur, conformément au code space saving.&lt;br /&gt;
         On incrément ensuie son compteur&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
         e = self.elements.get(k)&lt;br /&gt;
         if e is None:&lt;br /&gt;
             self.replace_min(k)&lt;br /&gt;
         self.incr(k)&lt;br /&gt;
&lt;br /&gt;
    def replace_min(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;replace the first symbol with minimal counter by `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        ....&lt;br /&gt;
&lt;br /&gt;
    def incr(self, k):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;increase counter for symbol `k`&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        E = self.elements[k] # On prend l&#039;élément k&lt;br /&gt;
        C = E.C # On prend aussi le compteur de l&#039;élément k&lt;br /&gt;
        &lt;br /&gt;
        E.pop() # On supprimer l&#039;élément de sa liste&lt;br /&gt;
        &lt;br /&gt;
        if C.E == E: # Dans le cas où le premier élément du compteur était l&#039;élément supprimé&lt;br /&gt;
            C.E = E.next # On définit la valeur suivante comme valeur du pointeur&lt;br /&gt;
                  &lt;br /&gt;
        if C.value + 1 == C.next.value: # On distingue deux cas. Dans le cas où le compteur suivant à la valeur du compteur actuel + 1&lt;br /&gt;
            C.next.E.add(E) # On ajoute l&#039;élément dans la liste d&#039;éléments du compteur suivant&lt;br /&gt;
            E.C = C.next # On définit le pointeur de cet élément sur le compteur suivant&lt;br /&gt;
        else:&lt;br /&gt;
            nC = Counter(C.value+1, E)&lt;br /&gt;
            C.add(nC) # On ajoute un nouveau compteur qui à la valeur +1 dans la liste des compteurs, et on définit sa liste d&#039;éléments avec notre Element&lt;br /&gt;
            E.C = nC # On redéfinit ici aussi le pointeur de notre élément sur ce compteur&lt;br /&gt;
            &lt;br /&gt;
            #Puisque l&#039;élément est dans seul dans une liste et qu&#039;il à conservé ses pointeurs, on les rédinit sur lui même&lt;br /&gt;
            E.prev = E &lt;br /&gt;
            E.next = E&lt;br /&gt;
        &lt;br /&gt;
        if C.E is None: # Si jamais, après avoir enlevé l&#039;élément de sa liste initiale, celle ci est vide&lt;br /&gt;
             C.pop() # On supprime le compteur de sa liste&lt;br /&gt;
             if self.lst_compteurs == C: # Si le compteur était le premier de la listes des compteurs&lt;br /&gt;
                self.lst_compteurs = C.next # On définit le premier élément de la liste des compteurs comme le compteur suivant&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15396</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15396"/>
		<updated>2024-05-14T22:23:56Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* Lien utiles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
*[https://github.com/TevaPhilippe05/Space_Saving-Project/blob/main/StreamSummary.py Implémentation entière de la structure de donnée Stream Summary sur Python]&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15385</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15385"/>
		<updated>2024-05-13T01:49:28Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(k) \text{ Aussi rapide que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
... (Lien vers le code sur github.)&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15384</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15384"/>
		<updated>2024-05-13T01:46:34Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* L&amp;#039;algorithme, principe et propriétés */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
1&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
2&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
... (Lien vers le code sur github.)&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15383</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15383"/>
		<updated>2024-05-13T01:46:17Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* L&amp;#039;algorithme, principe et propriétés */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
... (Lien vers le code sur github.)&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15382</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15382"/>
		<updated>2024-05-13T01:46:06Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* L&amp;#039;algorithme, principe et propriétés */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
io&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
... (Lien vers le code sur github.)&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15381</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15381"/>
		<updated>2024-05-13T01:45:33Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* L&amp;#039;algorithme, principe et propriétés */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, a, a, b, c} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{a, c, a, b, a} &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{1: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 1: c}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{2: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 2: b}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
... (Lien vers le code sur github.)&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15380</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15380"/>
		<updated>2024-05-13T01:43:46Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* L&amp;#039;algorithme, principe et propriétés */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    &amp;amp; \text{0: }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{2: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{3: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to b &amp;amp; \text{3: a }|\text{ 1: b}\\ \hline&lt;br /&gt;
    \to c &amp;amp; \text{3: a }|\text{ 2: c}\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
... (Lien vers le code sur github.)&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15379</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15379"/>
		<updated>2024-05-13T01:42:20Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : /* L&amp;#039;algorithme, principe et propriétés */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    &amp;amp; 0: | 0: \\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a }|\text{ 0:}\\ \hline&lt;br /&gt;
    \to a &amp;amp; 2: a | 0:\\ \hline&lt;br /&gt;
    \to a &amp;amp; 3: a | 0:\\ \hline&lt;br /&gt;
    \to b &amp;amp; 3: a | 1: b\\ \hline&lt;br /&gt;
    \to c &amp;amp; 3: a | 2: c\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
... (Lien vers le code sur github.)&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15378</id>
		<title>Calcul approché de l&#039;élément majoritaire, et autres algorithmes approchés</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Calcul_approch%C3%A9_de_l%27%C3%A9l%C3%A9ment_majoritaire,_et_autres_algorithmes_approch%C3%A9s&amp;diff=15378"/>
		<updated>2024-05-13T01:41:36Z</updated>

		<summary type="html">&lt;p&gt;Tphilippe : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Introduction au problème==&lt;br /&gt;
&lt;br /&gt;
Lorsque nous avons besoin de traiter de grandes quantités de données en temps réel, nous avons souvent besoin de déterminer les éléments qui sont les plus fréquents, les plus significatifs. L&#039;exemple le plus clair est celui des réseaux sociaux. Il faut déterminer parmi un flot de publications, lesquelles sont les plus adaptées pour l&#039;utilisateur. Nous nous sommes donc intéressés aux algorithmes qui permettent de déterminer les éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
==Solution commune : un problème==&lt;br /&gt;
&lt;br /&gt;
La solution intuitive et parfaitement correcte est l&#039;algorithme de majorité exacte qui compte précisément le nombre d’occurrences de chaque élément puis compare pour déterminer l&#039;élément majoritaire. Nous pouvons l&#039;écrire de différentes manières et l&#039;algorithme sera plus ou moins rapide en fonction de la structure de donnée que nous utilisons. Nous avons choisit ici d&#039;utiliser les dictionnaires, plus rapide que les listes.&lt;br /&gt;
&lt;br /&gt;
Voici un algorithme python rapide qui réalise satisfait notre demande :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def elem_maj_dict(lst:iter):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme qui renvoi l&#039;élément majoritaire avec 100% de réussite. On utilise ici les dictionnaires pour le rendre plus efficace.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    D = {}&lt;br /&gt;
    for k in lst:&lt;br /&gt;
        D[k] = D.get(k,0) + 1&lt;br /&gt;
            &lt;br /&gt;
    maxi = 0&lt;br /&gt;
    indice = 0&lt;br /&gt;
    &lt;br /&gt;
    for el in D:&lt;br /&gt;
        if D[el] &amp;gt; maxi:&lt;br /&gt;
            maxi = D[el]&lt;br /&gt;
            indice = el&lt;br /&gt;
            &lt;br /&gt;
    return indice&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n) &amp;amp; O(n)\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le problème est que même le plus efficace de ces algorithmes à un inconvénient : l&#039;occupation de la mémoire. Plus le nombre d’éléments est élevé, plus la mémoire requise est conséquente. Nous nous sommes donc demandé comment résoudre ce problème.&lt;br /&gt;
&lt;br /&gt;
==L&#039;algorithme Space Saving, une solution==&lt;br /&gt;
===L&#039;algorithme, introduction===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme Space Saving s&#039;incarne en une alternative à peu près aussi rapide mais qui ne stocke pas les éléments. La mémoire dont il a besoin est considérablement plus faible que celle de l&#039;algorithme classique.&lt;br /&gt;
&lt;br /&gt;
Cependant, le résultat n&#039;est pas toujours exact ! Pour certain cas d&#039;utilisation, cela n&#039;est pas un problème. L&#039;algorithme a des propriétés (qui seront détaillées ci-bas) qui garantisse un résultat correct ou proche de l&#039;optimum.&lt;br /&gt;
&lt;br /&gt;
Ainsi, dans le cas notamment des réseaux sociaux, si une publication sur 30 parmi celles suggérées à l&#039;utilisateur est fausse, cela n&#039;est pas un problème au vu du gain de mémoire gagné.&lt;br /&gt;
&lt;br /&gt;
Nous avons implémenté un algorithme Space Saving sur le langage python à l&#039;aide des dictionnaires, qui rendent les opérations plus rapides qu&#039;en utilisant les listes.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def ss_dict(lst:iter, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Algorithme space_saving qui renvoie les n éléments les plus fréquents&lt;br /&gt;
    Algorithme réalisé avec des dictionnaires. Adapté aux itérateurs&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    lst = iter(lst)&lt;br /&gt;
    compt = {} # Dictionnaires qui accueillera les éléments majoritaires&lt;br /&gt;
    compteur = 0&lt;br /&gt;
    &lt;br /&gt;
    while len(compt) &amp;lt; n: # Initialisation des compteurs&lt;br /&gt;
        lm = next(lst) ; compteur += 1&lt;br /&gt;
        compt[lm] = compt.get(lm, 0) + 1&lt;br /&gt;
&lt;br /&gt;
    for lm in lst:&lt;br /&gt;
        compteur += 1&lt;br /&gt;
        if lm in compt:&lt;br /&gt;
            compt[lm] += 1&lt;br /&gt;
        else:&lt;br /&gt;
            for elem in compt:&lt;br /&gt;
                if compt[elem] &amp;lt; compteur:&lt;br /&gt;
                    compteur = compt[elem] + 1&lt;br /&gt;
                    minim = elem&lt;br /&gt;
            &lt;br /&gt;
            del compt[minim]&lt;br /&gt;
            compt[lm] = compteur&lt;br /&gt;
 &lt;br /&gt;
    return compt, n&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===L&#039;algorithme, principe et propriétés ===&lt;br /&gt;
&lt;br /&gt;
On initialise n compteurs si l&#039;on souhaite n éléments, chacun d&#039;entre eux a une valeur associée à un élément. Lorsque l&#039;on a un nouvel élément, on a besoin de savoir quel élément a la plus petite valeur. On la remplace et on incrémente le compteur correspondant. Cela semble absurde à première vue mais cela devient logique lorsque l&#039;on comprend son fonctionnement. Nous avons la propriété suivante :&lt;br /&gt;
&lt;br /&gt;
Pour k compteurs, si un élément est présent plus que 1/k % des cas, alors l&#039;élément sera à coup sûr renvoyé dans la liste des éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Par exemple, si on a deux compteurs, 5 éléments et que l&#039;un d&#039;entre eux est présent 3 fois, il sera forcément renvoyé dans la liste des deux éléments majoritaires.&lt;br /&gt;
&lt;br /&gt;
Voici un exemple avec la distribution a, a, a, b, c et la distribution a, c, a, b, a&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    &amp;amp; 0: | 0: \\ \hline&lt;br /&gt;
    \to a &amp;amp; \text{1: a \| 0: }\\ \hline&lt;br /&gt;
    \to a &amp;amp; 2: a | 0:\\ \hline&lt;br /&gt;
    \to a &amp;amp; 3: a | 0:\\ \hline&lt;br /&gt;
    \to b &amp;amp; 3: a | 1: b\\ \hline&lt;br /&gt;
    \to c &amp;amp; 3: a | 2: c\\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Avec l’algorithme sous cette forme, nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
Nous avons la complexité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{array}{|l|l|}&lt;br /&gt;
    \hline&lt;br /&gt;
    \text{Temps d&#039;exécution} &amp;amp; \text{Occupation de mémoire} \\ \hline&lt;br /&gt;
    O(n \cdot k) \text{ Pire que l&#039;algorithme classique} &amp;amp; O(k) \text{ Mieux que l&#039;algorithme classique} \\ \hline&lt;br /&gt;
\end{array}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===La distribution Zipf===&lt;br /&gt;
&lt;br /&gt;
Il existe divers distributions de données dans lesquelles on a des éléments très peu présents et d&#039;autres présents en grande quantité. Nous avons notamment travaillé avec cette distribution (mais pas seulement) qui nous permettait de vérifier par application les résultats théoriques.&lt;br /&gt;
&lt;br /&gt;
Nous avons travaillé avec des données envoyées sous la forme d&#039;une liste, d&#039;un itérateur ou d&#039;un générateur. Implémenter un générateur a permis de s&#039;approcher d&#039;une application avec des données envoyées et traitées en temps réel.&lt;br /&gt;
&lt;br /&gt;
===La structure de donnée Stream Summary===&lt;br /&gt;
&lt;br /&gt;
Les chercheurs ont développé une structure de données nommée Stream Summary qui s&#039;implémente en programmation orientée objet. Elle possède la même complexité de mémoire que l&#039;algorithme Space Saving classique mais réduit son temps d’exécution.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Grace à cette structure de données, nous parvenons à obtenir cette complexité :&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
==Comparatifs de temps d&#039;éxécutions==&lt;br /&gt;
&lt;br /&gt;
Nous connaissions la complexité du temps d’exécution de nos trois algorithmes mais nous avons cherché à la vérifier. Nous avons donc modélisé des graphes :&lt;br /&gt;
&lt;br /&gt;
... (Graphes)&lt;br /&gt;
&lt;br /&gt;
==Quelles applications, quels choix ?==&lt;br /&gt;
&lt;br /&gt;
On utilisera l&#039;algorithme classique pour des ensembles de données de petite taille où la mémoire n&#039;est pas un problème. Celui-ci nous fournira un résultat exact qui comptera toutes les occurrences de chaque élément. Cependant plus il y aura de données puis celui-ci deviendra incompatible et plus l&#039;algorithme sera lent.&lt;br /&gt;
&lt;br /&gt;
On utilisera la structure de données Stream Summary pour traiter de grands volumes de données ou pour travailler en temps réel. Cependant, selon la distribution de celle-ci, il y aura des imprécisions. Il n&#039;est donc pas compatible avec toutes les situations.&lt;br /&gt;
&lt;br /&gt;
==Lien utiles==&lt;br /&gt;
&lt;br /&gt;
* [https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf Article de recherche sur les éléments majoritaires]&lt;br /&gt;
... (Lien vers le code sur github.)&lt;/div&gt;</summary>
		<author><name>Tphilippe</name></author>
	</entry>
</feed>