Maison >développement back-end >Tutoriel Python >Imiter une liste de dictionnaires avec une liste chaînée en Python
Je commencerai cet article par mon processus de réflexion et ma visualisation. Une expérience (vous pouvez choisir de l'appeler ainsi) qui m'a ouvert les yeux pour mieux comprendre ce que sont les listes chaînées. Cela m'a fait arrêter de le considérer comme abstrait, en suivant le cliché (données et suivant) avec lequel il est toujours expliqué.
Dernièrement, j'ai commencé à regarder le code d'un point de vue plus physique (POO comme on l'appelle habituellement). Cependant, le mien va au-delà des classes et des attributs ; J'ai commencé à écrire des étapes et des algorithmes avant chaque boucle while et for. J’en ai marre de me lancer dans des abstractions juste pour le plaisir. Cela m'a amené à essayer quelques choses supplémentaires, ce qui m'a appris quelques choses supplémentaires que je partagerai dans cet article.
Trois questions et réponses clés avant d'approfondir :
Pour répondre à la première ; les nœuds constituent une liste chaînée.
Pour la deuxième question ; pensez à un nœud dans une liste chaînée comme une voiture tirant une autre voiture. Dans ce scénario, supposons que les deux voitures contiennent des marchandises. La deuxième voiture est attachée à l’arrière de la première voiture avec une chaîne. Un nœud fait cela dans une liste chaînée en conservant des données comme les marchandises ou les passagers dans les voitures. Ce type de données peut être simple comme un entier ou une chaîne ou une structure de données plus complexe. En même temps, chaque voiture dispose d'un connecteur (chaîne) la reliant à la prochaine voiture sur la route. Dans une liste chaînée, il s'agit de l'attribut suivant qui pointe vers l'adresse mémoire du nœud suivant ou du nœud avant (dans une liste doublement chaînée). Une liste n'est pas une liste chaînée sans l'attribut suivant.
Chaque voiture ne connaît que la voiture directement devant ou derrière elle (selon le type de liste chaînée). La dernière voiture n’a pas de chaîne, ce qui signifie qu’elle ne pointe vers rien. Dans une liste chaînée, cela est souvent représenté par Aucun.
class Node: def __init__(self, data): self.data = data self.next = None
Pour répondre à la troisième question ; le nœud principal démarre une liste chaînée au moment même où la première voiture démarre le remorquage.
class LinkedList: def __init__(self): self.head = None
Jusqu'à présent, nous avons examiné les bases d'une liste chaînée. Nous allons maintenant aborder la principale raison pour laquelle j'ai écrit cet article.
Les structures de données intégrées de Python, telles que les listes et les dictionnaires, offrent des moyens flexibles de stocker et de gérer les données. Les listes nous permettent de stocker des séquences ordonnées, tandis que les dictionnaires nous permettent d'associer des clés à des valeurs pour un accès facile.
Dans cet article, nous explorerons comment créer une liste chaînée de type dictionnaire en utilisant la composition. Nous verrons des disparités dans l'utilisation de la mémoire entre notre liste chaînée de type dictionnaire et une liste de dictionnaires. Nous verrons également comment notre nœud peut obtenir un héritage de dict pour faire des instances de nœud de véritables dictionnaires. Tout cela dans le but de fournir plus de perspectives pour notre compréhension d'une liste chaînée.
Comme examiné précédemment, une liste chaînée est composée de nœuds. Ces nœuds ont chacun un segment de données et un segment suivant. Les données peuvent être simples comme une chaîne ou un entier, ou complexes.
Simple :
class Node: def __init__(self, data): self.data = data self.next = None
La classe Node (représentée par My_Int) a data et next comme attributs.
class LinkedList: def __init__(self): self.head = None
Je vais traiter d'un cas complexe dans cet article :
Complexe :
class My_Int: def __init__(self, data): self.data=data self.next=None
La classe de nœuds (représentée par My_Dict) contient plusieurs attributs : nom d'utilisateur, teint et suivant. **kwargs comme argument, permet aux méthodes d'accepter n'importe quel nombre d'arguments de mots-clés sans définir explicitement chacun d'entre eux.
Chaque nœud ne stocke pas seulement un seul élément de données, mais combine plusieurs éléments (nom d'utilisateur et teint) en un seul, ce qui le rend plus complexe qu'une structure de données de base comme un entier ou une chaîne.
Nous allons maintenant créer une classe My_List qui gérera une liste chaînée d'instances My_Dict. Il faut un attribut head. Cette tête est généralement le premier nœud qui initialise une liste chaînée.
class My_Int: def __init__(self, data): self.data=data self.next=None class LinkedList: def __init__(self): self.head=None def insert(self, node): if self.head==None: self.head = node return last_node = self.head while(last_node.next): last_node =last_node.next last_node.next = node def traverse(self): current_node = self.head while(current_node): print(current_node.data, end="->") current_node = current_node.next print("None") node1 = My_Int(5) node2 = My_Int (10) node3 = My_Int(15) linked_list = LinkedList() linked_list.insert(node1) linked_list.insert(node2) linked_list.insert(node3) linked_list.traverse()
Cette classe fournit également une méthode d'insertion pour ajouter de nouvelles entrées de nœud à la fin.
Nous créons également ici une instance de My_Dict (qui est un nœud). My_List agira comme un conteneur pour les objets My_Dict dans une structure de liste chaînée. Chaque instance My_Dict est connectée par une référence suivante qui permet à My_List de gérer dynamiquement le parcours et l'insertion des instances My_Dict. Cela illustre fondamentalement la composition.
Après la création d'une instance My_Dict, on vérifie que la liste n'est pas vide en vérifiant la présence de la tête. Si le head n'est pas présent, cela signifie que la liste est vide, nous initialisons donc self.head comme nouveau nœud (qui est my_dict). Le return quitte alors immédiatement la fonction. Pas besoin d'exécuter davantage.
class My_Dict: #dict keys as attributes def __init__(self, **kwargs): self.username = kwargs['username'] self.complexion = kwargs['complexion'] self.next = None
La ligne après le retour s'exécute lorsqu'il y avait un nœud auparavant dans la liste et que nous voulons insérer un nouveau nœud. Nous initialisons ce nœud last_dict en tant que head et celui-ci sera utilisé pour trouver le dernier nœud (la fin de la liste) afin que le nouveau nœud puisse y être ajouté. La boucle while parcourt la liste jusqu'à ce qu'elle atteigne le dernier nœud. Tant que last_dict.next n'est pas égal à None, il passe au nœud suivant en définissant last_dict = lastdict.next.
Enfin last_dict.next = my_dict ajoute my_dict à la fin de la liste pour terminer l'insertion. Une fois que nous savons que last_dict.next vaut None (c'est-à-dire que nous sommes au dernier nœud), nous y attachons my_dict.
Nous abordons maintenant la fonction traversée :
class Node: def __init__(self, data): self.data = data self.next = None
La fonction traverse parcourt chaque nœud de la liste chaînée et effectue une action (dans ce cas, l'impression) sur chaque nœud, de la tête à la fin. La méthode fournit une vue séquentielle de tous les nœuds de la liste.
Regardez le code complet ci-dessous :
class LinkedList: def __init__(self): self.head = None
class My_Int: def __init__(self, data): self.data=data self.next=None
Notre implémentation ci-dessus peut être considérée comme une liste chaînée personnalisée d'objets de type dictionnaire, mais elle est structurée différemment d'une liste typique de dictionnaires Python standard. Voici les points à noter :
Il est toujours bon d'avoir un algorithme lors de l'écriture du code. C’est ce qu’on appelle les mesures prises. J'aurais peut-être dû les écrire avant le code ci-dessus. Mais je voulais juste montrer d’abord la différence entre la liste chaînée clichée de tous les jours et un type plus complexe. Sans plus tarder, voici les étapes.
Nous allons maintenant comparer ce que nous avons ci-dessus avec une liste de dictionnaires Python en examinant quelques points :
Liste des dictionnaires : en Python, une liste de dictionnaires stocke chaque dictionnaire en tant qu'élément dans une structure de liste contiguë, rendant chaque dictionnaire accessible via un index.
class Node: def __init__(self, data): self.data = data self.next = None
Liste liée de dictionnaires : notre code utilise une structure de liste chaînée, où chaque nœud (instance My_Dict de type dictionnaire) contient une référence au nœud suivant plutôt que d'utiliser un stockage en mémoire contiguë. Ceci est plus efficace en mémoire pour les grandes listes si les éléments changent fréquemment, mais c'est plus lent pour l'accès par position.
Liste chaînée de dictionnaires : dans notre liste chaînée, l'accès à un élément nécessite un parcours (temps O(n)), car nous devons itérer nœud par nœud. L'insertion à la fin est également O(n) car nous devons trouver le dernier nœud à chaque fois. Cependant, l'insertion au début peut être O(1) puisque nous pouvons définir le nouveau nœud comme tête.
Liste de dictionnaires : une liste Python utilise plus de mémoire pour stocker les dictionnaires dans un bloc contigu, car chaque élément est stocké séquentiellement. La mémoire est allouée dynamiquement pour les listes Python, redimensionnant et copiant parfois la liste lorsqu'elle s'agrandit. Nous pouvons le prouver dans le code en utilisant le module sys :
class LinkedList: def __init__(self): self.head = None
class My_Int: def __init__(self, data): self.data=data self.next=None
Liste chaînée de dictionnaires : la liste chaînée utilise efficacement la mémoire pour chaque nœud car elle n'alloue de la mémoire que si nécessaire. Cependant, cela nécessite un espace supplémentaire pour la référence suivante dans chaque nœud.
class My_Int: def __init__(self, data): self.data=data self.next=None class LinkedList: def __init__(self): self.head=None def insert(self, node): if self.head==None: self.head = node return last_node = self.head while(last_node.next): last_node =last_node.next last_node.next = node def traverse(self): current_node = self.head while(current_node): print(current_node.data, end="->") current_node = current_node.next print("None") node1 = My_Int(5) node2 = My_Int (10) node3 = My_Int(15) linked_list = LinkedList() linked_list.insert(node1) linked_list.insert(node2) linked_list.insert(node3) linked_list.traverse()
class My_Dict: #dict keys as attributes def __init__(self, **kwargs): self.username = kwargs['username'] self.complexion = kwargs['complexion'] self.next = None
D'après ce qui précède, nous pouvons voir la différence en octets ; 776 et 192.
Dans notre code, les instances My_Dict sont des objets de type dictionnaire plutôt que de véritables dictionnaires.
Voici quelques raisons :
L'attribut suivant relie les instances My_Dict entre elles, les faisant davantage ressembler à des nœuds dans une liste chaînée qu'à des dictionnaires autonomes. Cet attribut suivant n'est pas une fonctionnalité que nous trouverions dans un dictionnaire classique.
Vous trouverez ci-dessous un aperçu de la manière dont nous pouvons hériter de la classe dict :
class My_List: #manager def __init__(self): self.head=None
def insert(self, **kwargs): my_dict = My_Dict(**kwargs) #instantiate dict #check if list is empty if self.head==None: self.head=my_dict return last_dict = self.head while(last_dict.next): last_dict = last_dict.next last_dict.next = my_dict #makes the insertion dynamic
La première ligne importe Dict depuis le module de saisie de Python. Cela indique que My_Dict est un dictionnaire spécialisé.
class Node: def __init__(self, data): self.data = data self.next = None
La classe My_Dict hérite de dict, ce qui signifie qu'elle aura les propriétés d'un dictionnaire mais pourra être personnalisée.
class LinkedList: def __init__(self): self.head = None
Jetons un œil au constructeur :
class My_Int: def __init__(self, data): self.data=data self.next=None
__init__ initialise une instance de My_Dict avec les attributs de nom d'utilisateur et de teint. super().__init_() appelle le constructeur de la classe parent Dict. self['username'] = username et self['complexion'] = complexion stockent le nom d'utilisateur et le complexion sous forme d'entrées de dictionnaire, permettant d'accéder aux instances My_Dict comme un dictionnaire (par exemple, new_dict['username']). Il y a aussi un
attribut suivant initialisé sur Aucun, établissant une structure de liste chaînée en permettant à chaque instance My_Dict de se lier à une autre.
class My_Int: def __init__(self, data): self.data=data self.next=None class LinkedList: def __init__(self): self.head=None def insert(self, node): if self.head==None: self.head = node return last_node = self.head while(last_node.next): last_node =last_node.next last_node.next = node def traverse(self): current_node = self.head while(current_node): print(current_node.data, end="->") current_node = current_node.next print("None") node1 = My_Int(5) node2 = My_Int (10) node3 = My_Int(15) linked_list = LinkedList() linked_list.insert(node1) linked_list.insert(node2) linked_list.insert(node3) linked_list.traverse()
La méthode ci-dessus remplace la méthode des clés de dict, lui permettant de renvoyer toutes les clés dans My_Dict. Utiliser super().keys() appelle le parent
Keys(), garantissant un comportement standard du dictionnaire.
class My_Dict: #dict keys as attributes def __init__(self, **kwargs): self.username = kwargs['username'] self.complexion = kwargs['complexion'] self.next = None
Dans la méthode d'insertion de classe MyList, nous pouvons voir comment nous faisons en sorte que l'instance créée de notre dictionnaire présente le comportement du dictionnaire. Nous y enchaînons la méthode keys() et nous évaluons également la clé du nom d'utilisateur avec elle. Nous faisons cela dans les blocs if et else. Dans le bloc if, il imprime les clés du nœud principal et le nom d'utilisateur. Dans le bloc else, il imprime les clés des autres nœuds et le nom d'utilisateur des autres nœuds.
class My_List: #manager def __init__(self): self.head=None
Dans la méthode traversée ci-dessus dans la compréhension du dictionnaire :
def insert(self, **kwargs): my_dict = My_Dict(**kwargs) #instantiate dict #check if list is empty if self.head==None: self.head=my_dict return last_dict = self.head while(last_dict.next): last_dict = last_dict.next last_dict.next = my_dict #makes the insertion dynamic
Nous construisons un dictionnaire avec chaque paire clé-valeur de current_dict. current_dict = getattr(current_dict, 'next', None) passe au nœud suivant, en continuant jusqu'à ce que current_dict devienne None.
Remarque : en ce qui concerne l'utilisation de la mémoire, faire en sorte que notre classe hérite de dict signifie plus d'utilisation de la mémoire. Contrairement à la liste chaînée de nœuds de type dictionnaire que nous avons créée précédemment.
Le but de cet article est de fournir plus de perspectives et d'informations sur ce que sont les listes chaînées, au-delà de ce que je vois habituellement comme expliqué. Je n’étais pas innovant. J'expérimentais simplement le code, essayant de fournir plus d'informations à ceux qui pourraient le considérer comme trop abstrait. Cependant, j'aimerais savoir de la part de programmeurs plus expérimentés, quels pourraient être les inconvénients s'ils étaient utilisés ou lorsqu'ils étaient utilisés dans le passé.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!