Pythonbased implementations for many data structures.
Project description
Abstract and Treelike Data Structures  Python
The repository contains Pythonbased implementations for popular data structures.
Installation:
Please use 'pip install pythonicdatastructures'.
Issues:
Please register any issues you find in the github repository so that they can be fixed as soon as possible.
Requirements:
There are no dependencies on external libraries. However, a Python 3.x version is required.
Docs:
Navigate to data structures: Stack, Queue, Min Binary Heap,
Max Binary Heap, Priority Queue, Duplicate Priority Queue, Graph
Data Structures
Implementation for Stack, Queue, Max Binary Heap, Min Binary Heap, Priority Queue and Graph in Python
These are located in the AbstractDataStructures.py and TreeDataStructures.py modules
 Stack (FirstInLastOut)
The Stack's implementation wraps around the python deque object, but is also generic: you can specify the type of elements in the stack in the constructor. If not specified, it is set to None and elements of any type can be added to the stack. The implementation includes all the common operations of a stack: peek, push, pop, size, etc.
API :
from DataStructures.AbstractDataStructures import Stack # import the stack data structure stack = Stack() # type is set to None, items of any types can be added stack = Stack(elements_type = int) # type is set to int, hence only integers can be pushed stack.size # the number of elements in the stack len(stack) # same as stack.size str(stack) # returns the string representation of the python deque object containing the elements of the stack item = "test_item" stack.contains(item) # returns True if the item is in the stack and False otherwise # contains raises a StackTypeError if the type of the stack is not None and is different than the type of the parameter boolean = item in stack # same as boolean = stack.contains(item) stack.type # returns the type of the elements in the stack, None if no type is specified when initialised stack.peek() # returns the last element that was added to the stack, but doesn't remove it # peek returns None if there are no elements in the stack stack.pop() # same as peek(), but removes the last element that was added to the stack # pop raises an EmptyStackError if there are no elements in the stack element = "test_item" stack.push(element) # pushes the element to the top of the stack # push raises a StackTypeError if the stack has a specified type for elements and the argument is not of that type # the implementation includes an iterator for element in stack: print(element) # keep in mind that the iterator uses stack.pop() to get the next element, hence # after the iteration is over the stack would be empty stack.remove(element) # removes the element from the stack # raises a StackTypeError if the stack has a specified type for elements and the argument is not of that type # raises a StackElementError if the stack doesn't contain the element specified as argument
 Queue (FirstInFirstOut)
The Queue's implementation wraps around the python deque object, but is also generic: you can specify the type of elements in the queue in the constructor. If not specified, it is set to None and elements of any type can be added to the queue. The implementation includes all the common operations of a queue: enqueue, dequeue, peek, size, etc.
API :
from DataStructures.AbstractDataStructures import Queue # import the queue data structure queue = Queue() # type is set to None, items of any types can be added queue = Queue(elements_type = str) # type is set to str, hence only strings can be enqueued queue.size # the number of elements in the queue len(queue) # same as queue.size str(queue) # returns the string representation of the python deque object containing the elements of the queue item = "test_item" queue.contains(item) # returns True if the item is in the queue and False otherwise # contains raises a QueueTypeError if the type of the queue is not None and is different # than the type of the parameter boolean = item in queue # same as boolean = queue.contains(item) queue.type # the type of the elements in the queue, None if no type is specified when initialised queue.peek() # returns the first element that was added to the queue, but doesn't remove it # peek returns None if there are no elements in the queue queue.dequeue() # same as peek(), but removes the first element that was added to the queue # dequeue raises a EmptyQueueError if there are no elements in the queue element = "test_element" queue.enqueue(element) # enqueues the element to the back of the queue # enqueue raises a QueueTypeError if the queue has a specified type for elements # and the argument is not of that type # the implementation includes an iterator for element in queue: print(element) # keep in mind that the iterator uses queue.dequeue() to get the next element, hence # after the iteration is over the queue would be empty queue.remove(element) # removes the element from the queue # raises a QueueTypeError if the queue has a specified type for elements and the argument is not of that type # raises a QueueElementError if the queue doesn't contain the element specified as argument
 BinaryHeap
The BinaryHeap's implementation is generic: you can specify the type of elements in the heap in the constructor. If not specified, it is set to int, hence only integers can be added to the heap. The BinaryHeap class is abstract. You cannot instantiate it. The implementation includes two types of heaps, which you can use: MinBinaryHeap and MaxBinaryHeap.
MinBinaryHeap  a heap with its root being the minimum element
MinBinaryHeap implements the common operations of a heap: add, replace_root, remove_min, peek_min, size, etc.
MaxBinaryHeap  a heap with its root being the maximum element
MaxBinaryHeap implements the common operations of a heap: add, replace_root, remove_max, peek_max, size, etc.
from DataStructures.AbstractDataStructures import MinBinaryHeap # import the min heap min_heap = MinBinaryHeap() # type is set to default  int, hence only integers can be added # creates an empty heap min_heap = MinBinaryHeap(str) # type is set to str, hence only strings can be added # creates an empty heap min_heap.size # the number of elements in the heap len(min_heap) # same as min_heap.size str(min_heap) # returns a string of the list of elements in the heap min_heap.type # the type of elements in the heap element = "test_element" min_heap.add(element) # adds the element to the min binary heap on the place it should be located # add raises a BinaryHeapTypeError if the type of the argument is not the same as the type of the elements in the heap min_heap.peek_min() # returns the minimum element (the root), but doesn't remove it from the heap # returns None if heap is empty min_heap.remove_min() # returns the minimum element (the root) and removes it from the heap # the method replaces the root with the second minimum element in the heap # it raises a EmptyBinaryHeapError if the heap is empty # returns the minimum element (the root) and removes it from the heap, by replacing it with the element argument min_heap.replace_root(element) # same as min_heap.remove_min() followed by min_heap.add(element), but replace_root() is faster # raises a BinaryHeapTypeError if the type of the argument is not the same as the type of the elements in the heap # raises a EmptyBinaryHeapError if the heap is empty min_heap.get_sorted_elements() # returns a list with the sorted elements from the heap, the heap remains unchanged # the order is ascending; returns an empty list if the heap is empty # the iterator goes through each element in the heap in ascending order for element in min_heap: print(element) # keep in mind that using the iterator will remove each element you go through from the heap, since it uses remove_min() # to generate the next element, hence when the iterator is finished the heap would be empty; # if you want to keep the elements in the heap, use get_sorted_elements() (although it's slightly slower) # another example with the iterator heap_iter = iter(min_heap) while True: try: print(next(heap_iter)) except StopIteration: break min_heap.size # will return 0 after iteration is finished, as explained above old_element, new_element = 10, 100 min_heap.replace(old_element, new_element) # replaces the old element with the new element and readjusts the heap after # the replacement # raises a BinaryHeapTypeError if the type of the first or the second argument is not the same as the type of the # elements in the heap # raises a BinaryHeapElementError if the old_element argument is not contained in the heap # raises a EmptyBinaryHeapError if the heap is empty min_heap.remove(element) # removes the element and readjusts the heap after deletion # raises a BinaryHeapTypeError if the type of the argument is not the same as the type of the elements in the heap # raises a BinaryHeapElementError if the element argument is not contained in the heap # raises a EmptyBinaryHeapError if the heap is empty
from DataStructures.AbstractDataStructures import MaxBinaryHeap # import the max heap max_heap = MaxBinaryHeap() # type is set to default  int, hence only integers can be added # creates an empty heap max_heap = MaxBinaryHeap(str) # type is set to str, hence only strings can be added # creates an empty heap max_heap.size # the number of elements in the heap len(max_heap) # same as max_heap.size str(max_heap) # returns a string of the list of elements in the heap max_heap.type # the type of elements in the heap element = "test_element" max_heap.add(element) # adds the element to the max binary heap on the place it should be located # add raises a BinaryHeapTypeError if the type of the argument is not the same as the type of the elements in the heap max_heap.peek_max() # returns the maximum element (the root), but doesn't remove it from the heap # returns None if heap is empty max_heap.remove_max() # returns the maximum element (the root) and removes it from the heap # the method replaces the root with the second maximum element in the heap # it raises a EmptyBinaryHeapError if the heap is empty # returns the maximum element (the root) and removes it from the heap, by replacing it with the element argument max_heap.replace_root(element) # same as max_heap.remove_max() followed by max_heap.add(element), but replace_root() is faster # raises a BinaryHeapTypeError if the type of the argument is not the same as the type of the elements in the heap # raises a EmptyBinaryHeapError if the heap is empty max_heap.get_sorted_elements() # returns a list with the sorted elements from the heap, the heap remains unchanged # the order is descending; returns an empty list if the heap is empty # the iterator goes through each element in the heap in descending order for element in max_heap: print(element) # keep in mind that using the iterator will remove each element you go through from the heap, since it uses remove_max() # to generate the next element, hence when the iterator is finished the heap would be empty; # if you want to keep the elements in the heap, use get_sorted_elements() (although it's slightly slower) # another example with the iterator heap_iter = iter(max_heap) while True: try: print(next(heap_iter)) except StopIteration: break max_heap.size # will return 0 after iteration is finished, as explained above old_element, new_element = 10, 100 max_heap.replace(old_element, new_element) # replaces the old element with the new element and readjusts the heap after # the replacement # raises a BinaryHeapTypeError if the type of the first or the second argument is not the same as the type of the # elements in the heap # raises a BinaryHeapElementError if the old_element argument is not contained in the heap # raises a EmptyBinaryHeapError if the heap is empty max_heap.remove(element) # removes the element and readjusts the heap after deletion # raises a BinaryHeapTypeError if the type of the argument is not the same as the type of the elements in the heap # raises a BinaryHeapElementError if the element argument is not contained in the heap # raises a EmptyBinaryHeapError if the heap is empty
 Priority Queue
The Priority Queue's implementation is generic: you can specify the type of elements in the queue in the constructor. If not specified, it is set to None, hence objects of all types can be added to the priority queue. You can also set the reverse argument in the constructor. If reverse is set to False (default) the queue dequeues the element with the greatest priority, else if the reverse argument is set to True  it dequeues the element with the lowest priority. The implementation includes all the common operations of a priority queue: enqueue, dequeue, peek, size, etc.
API :
from DataStructures.AbstractDataStructures import PriorityQueue # import the priority queue data structure priority_queue = PriorityQueue() # type is set to default None, hence objects of all types can be enqueued to the queue # the reverse argument is set to default False, hence dequeue returns the element with the highest priority priority_queue = PriorityQueue(elements_type=str, reverse=True) # type is set to str, hence only strings can be enqueued # the reverse argument is set to True, hence dequeue returns the element with the lowest priority priority_queue.size # the number of elements in the queue len(priority_queue) # same as priority_queue.size str(priority_queue) # returns a string of the dictionary linking priorities with elements in the queue priority_queue.type # the type of elements that can be enqueued in the priority queue # if this method returns None, objects of all types can be enqueued priority_queue.reversed # True if the queue dequeues the element with the lowest priority # returns False if the queue dequeues the element with the highest priority priority = 10 priority_queue.contains_priority(priority) # returns True if the queue has an element linked to the given priority and False otherwise # contains raises a PriorityQueueTypeError if type of priority is not int element = "test_element" priority_queue.contains_element(element) # returns True if an element is contained in the queue # raises PriorityQueueTypeError if priority_queue.type is not None and is different than the type of the given element boolean = element in priority_queue # same as priority_queue.contains_element(element) item = "test_item" priority_queue.enqueue(item, priority) # enqueues the given item and links it the given priority # raises PriorityQueueTypeError if type(priority) is not int # raises PriorityQueueTypeError if priority_queue.type is not None and is different than the type of the given item # keep in mind that if there is another element linked to the same priority, the old element will be replaced # by the new element priority_queue.enqueue("first_item", 5) priority_queue.enqueue("second_item", 5) # doing this will link priority 5 to str object "second_item" while str object "first_item" will be ignored and # removed from the queue # Another thing to note is that you can enqueue the same element to different priorities priority_queue.enqueue("item", 10) priority_queue.enqueue("item", 11) priority_queue.peek() # returns element with minimum or maximum priority in the queue, but doesn't remove it from the queue # if priority_queue.reversed is False, it returns the element with the maximum priority # if priority_queue.reversed is True, it returns the element with the minimum priority # returns None if the queue is empty priority_queue.dequeue() # same as priority_queue.peek(), but removes the returned element from the queue # raises a EmptyPriorityQueueError if the queue is empty priority_queue.get_element(priority) # returns the element linked to the given priority # returns None if no element is linked to this priority # raises a PriorityQueueTypeError if type(priority) is not int # the implementation includes an iterator too for item in priority_queue: print(item) # keep in mind that the iterator uses priority_queue.dequeue() to get the next element, hence after the iteration # is finished the priority_queue will be empty priority_queue.replace_priority(element, priority) # replaces the given element's priority with the new priority argument # returns a boolean representing whether the element's priority has been replaced # raises PriorityQueueTypeError if type(priority) is not int # raises PriorityQueueTypeError if priority_queue.type is not None and is different than the type of the given element # raises PriorityQueueElementError if the element is not contained in the queue # if there is another element already assigned to the new priority, the old element will be replaced with the element # given as argument, thus the old element will be ignored and removed # you can also pass a third argument to the replace_priority method  comparison comparison_type = 1 priority_queue.replace_priority(element, priority, comparison=comparison_type) # returns a boolean representing whether the element's priority has been replaced # by doing so the priority of the element will only be replaced if a certain type of comparison between # the two priorities holds # if comparison is 1, the priorities will be replaced if the new priority is greater than the old priority # if comparison is 1, the priorities will be replaced if the new priority is less than the old priority # if comparison is None (default), the priorities will always be replaced # raises ValueError if comparison is not 1, 1 or None priority_queue.remove_element(element) # finds and removes the element from the queue # raises PriorityQueueTypeError if priority_queue.type is not None and is different than the type of the given element # raises PriorityQueueElementError if the queue doesn't contain the element
 Duplicate Priority Queue
The Duplicate Priority Queue behaves exactly as the normal Priority Queue with the only difference being that it allows elements with duplicated priorities. This, however is not true for the normal Priority Queue, since in its implementation if you enqueue an element with a priority that is already linked to some old element, then the old element would be replaced by the new enqueued element. By using a duplicate priority queue, no elements are ignored. Instead, if you dequeue and there are two elements with the same priority, then they will be dequeued in the order they were enqueued.
API :
from DataStructures.AbstractDataStructures import DuplicatePriorityQueue # import the priority queue data structure queue = DuplicatePriorityQueue() # type is set to default None, hence objects of all types can be enqueued to the queue # the reverse argument is set to default False, hence dequeue returns the element with the highest priority queue = DuplicatePriorityQueue(elements_type=str, reverse=True) # type is set to str, hence only strings can be enqueued # the reverse argument is set to True, hence dequeue returns the element with the lowest priority queue.size # the number of elements in the queue, # elements with the same priority are NOT counted as one element, but as ordinary elements len(queue) # same as queue.size str(queue) # returns a string of the dictionary linking priorities with elements in the queue # keep in mind that if there is a priority linked to more than one element, the string representation will return # the priority linked to a Queue object queue.type # the type of elements that can be enqueued in the priority queue # if this method returns None, objects of all types can be enqueued queue.reversed # returns True if the queue dequeues the element with the lowest priority # returns False if the queue dequeues the element with the highest priority priority = 10 queue.contains_priority(priority) # returns True if the queue has an element or elements linked to the given priority and False otherwise # contains raises a PriorityQueueTypeError if type of priority is not int element = "test_element" queue.contains_element(element) # returns True if an element is contained in the queue # raises PriorityQueueTypeError if queue.type is not None and is different than the type of the given element boolean = element in queue # same as queue.contains_element(element) item = "test_item" queue.enqueue(item, priority) # enqueues the given item and links it the given priority # raises PriorityQueueTypeError if type(priority) is not int # raises PriorityQueueTypeError if priority_queue.type is not None and is different than the type of the given item # in this implementation of a priority queue, if there is already an item with the given priority in the queue, then both # items will be retained and when dequeueing they will be dequeued in the order they were enqueued queue.enqueue("first_item", 5) queue.enqueue("second_item", 5) queue.dequeue() # dequeues "first_item" queue.dequeue() # dequeues "second_item" # Another thing to note is that you can enqueue the same element to different priorities queue.enqueue("item", 10) queue.enqueue("item", 11) queue.peek() # returns element with minimum or maximum priority in the queue, but doesn't remove it from the queue # if priority_queue.reversed is False, it returns the element with the maximum priority # if priority_queue.reversed is True, it returns the element with the minimum priority # returns None if the queue is empty # if there are more than one elements with the same priority, peek() will return the first element that was enqueued queue.dequeue() # same as priority_queue.peek(), but removes the returned element from the queue # raises a EmptyPriorityQueueError if the queue is empty # if there are more than one elements with the same priority, dequeue() will return and remove them in the order they were # enqueued queue.get_element(priority) # returns the element linked to the given priority # returns None if no element is linked to this priority # raises a PriorityQueueTypeError if type(priority) is not int # if there are more than one elements with the same priority, get() will return the first element that was enqueued # the implementation includes an iterator too for item in queue: print(item) # keep in mind that the iterator uses priority_queue.dequeue() to get the next element, hence after the iteration # is finished the queue will be empty queue.replace_priority(element, priority) # replaces the given element's priority with the new priority argument # raises PriorityQueueTypeError if type(priority) is not int # raises PriorityQueueTypeError if queue.type is not None and is different than the type of the given element # raises PriorityQueueElementError if the element is not contained in the queue # in this implementation duplicated priorities are allowed, hence no elements will be ignored even if there is already # an element assigned to the new priority # you can also pass a third argument to the replace_priority method  comparison comparison_type = 1 queue.replace_priority(element, priority, comparison=comparison_type) # by doing so the priority of the element will only be replaced if a certain type of comparison between # the two priorities holds # if comparison is 1, the priorities will be replaced if the new priority is greater than the old priority # if comparison is 1, the priorities will be replaced if the new priority is less than the old priority # if comparison is None (default), the priorities will always be replaced # raises ValueError if comparison is not 1, 1 or None queue.remove_element(element) # finds and removes the element from the queue # raises PriorityQueueTypeError if queue.type is not None and is different than the type of the given element # raises PriorityQueueElementError if the queue doesn't contain the element
 Graph
The graph's implementation is generic: you can specify the type of elements in the graph in the constructor. If not specified, it is set to None, hence objects of all types can be added to the graph. You can also set the directed, oriented and weighted arguments in the constructor if you want to have a graph with a special feature. By default, those arguments are set to False. Keep in mind that you cannot initialize a graph, which is oriented and not directed at the same time.
Nodes of the graph are stored in a list and a set, which allows fast checking whether the graph contains a certain node in cost of memory. The graph doesn't support duplicate node values
Edges of the graph are stored in a square matrix: 2dimensional list which is resized automatically when needed. The initial length of the edges list is 5. A value of None in the matrix represents the absence of an edge. If the graph is not weighted a value of 1 represents the presence of a node. If the graph is weighted, an edge would be represented by its weight in the matrix and it must be either float or int.
The indices in the 2dimensional list are the indices of the nodes in the list. E.g.
if the list of nodes is:
nodes = [5.5, "word", 100]
and we have a nondirected and nonweighted graph with an edge between 5.5 and 100, the edges matrix will be
edges = [ [None, None, 1, None, None], [None, None, None, None, None], [1, None, None, None, None], [None, None, None, None, None], [None, None, None, None, None] ]
Note the initial size of the matrix, which is 5 by 5 matrix. The indices of 5.5 and 100 in the list of nodes are 0 and 2 respectively and the graph is not directed. That's why edges[0][2] = edges[2][0] = 1.
API :
from DataStructures.AbstractDataStructures import Graph # import the graph data structure graph = Graph() # initialize a graph with None elements type, hence all types of elements can be added to the graph # the initialized graph is also neither directed, nor oriented, nor weighted graph = Graph(elements_type=int, directed=True, oriented=False, weighted=True) # only integers can be added to the initialized graph; the graph is directed, but not oriented; the graph is also weighted graph = Graph(elements_type=str, directed=False, oriented=True, weighted=True) # this raises an InvalidGraphError since a graph cannot be oriented and not directed at the same time graph = Graph(float, True, True, True) # only floats can be added to the initialized graph; the graph is directed, oriented and weighted graph.size # the number of elements in the graph len(graph) # same as graph.size str(graph) # returns a string in the format 'Graph: directed  boolean, oriented  boolean, weighted  boolean' graph.type # the type of node values in the graph, returns None if all types of elements are allowed # if this method doesn't return None, only nodes of the returned type can be added to the graph graph.directed # True if the graph is directed and False otherwise graph.oriented # True if the graph is oriented and False otherwise graph.weighted # True if the graph is weighted and False otherwise item = "test_element" graph.contains(item) # returns True if item is in the set of nodes of the graph and False otherwise # raises a GraphTypeError if the type of the graph is not None and is different than the type of the argument boolean = item in graph # same as graph.contains(item) first_item = "first_test_item" second_item = "second_test_item" graph.contains_edge(first_item, second_item) # returns True if an edge from first_item to second_item exists # raises a GraphTypeError if the type of the graph is not None and is different than the type of any of the arguments # raises a GraphElementError if first_item or second_item is not a node that the graph contains # if the graph is not directed the result will be the same even if you reverse the order of the arguments graph.get_edge_weight(first_item, second_item) # raises a GraphTypeError if the type of the graph is not None and is different than the type of any of the arguments # raises a GraphElementError if first_item or second_item is not a node that the graph contains # raises a GraphEdgeError if the graph is not weighted # raises a GraphEdgeError if an edge between first_item and second_item doesn't exist # if the graph is not directed the result will be the same even if you reverse the order of the arguments graph.nodes() # returns a deep copy of the list of nodes of the graph # a deep copy is returned to avoid manual changes of the graph by changing the elements in the returned list graph.add_node(item) # adds item to the nodes of the graph if it is not already added # raises a GraphTypeError if the type of the graph is not None and is different than the type of the argument # raises an GraphElementError if item is None # if item is already added as a node to the graph, the function does nothing graph.remove_node(item) # raises a GraphTypeError if the type of the graph is not None and is different than the type of the argument # raises a GraphElementError if item is not a node in the graph # remove a node in the graph also removes all edges related to this node (going to and from this node) old_node = "test_old_node" new_node = "test_new_node" graph.replace_node(old_node, new_node) # replaces old_node with new_node in the graph list of nodes if possible # raises a GraphTypeError if the type of the graph is not None and is different than the type of any of the arguments # raises a GraphElementError if old_node is not a node the graph contains # raises a GraphElementError if new_node is a node the graph contains, since duplicate nodes are not allowed # the replacing of a node in the graph doesn't affect the edges in the graph, e.g. connected_nodes = graph.edges_of(old_node) graph.replace_node(old_node, new_node) new_connected_nodes = graph.edges_of(new_node) print(connected_nodes == new_connected_nodes) # will print True, since edges of the old node are not affected, only the value is replaced # the method is useful, since it retains the edges of the old node # and is faster than first removing the old node and then adding the new node graph.edges() # returns a deep copy of the square matrix (2D list) representing the edges of the graph # a deep copy is returned to avoid manual changes of the graph by changing the elements in the returned list graph.edges_of(item) # returns a list of all nodes to which there is an edge from the argument # returns an empty list if there are no such nodes # raises a GraphTypeError if the type of the graph is not None and is different than the type of the argument # raises a GraphElementError if the item if not a node in the graph edge_weight = "test_edge_weight" graph.add_edge(first_item, second_item, edge_weight) # adds an edge from first_item to second_item with the given edge_weight if appropriate # edge_weight should only be specified if the graph is weighted, otherwise, just skip this argument (set to None by default) # raises a GraphTypeError if the type of the graph is not None and is different than the type of any of the arguments # raises a GraphTypeError if edge_weight is specified and is not of type float or int # raises a GraphElementError if first_item or second_item is not a node that the graph contains # raises a GraphEdgeError if the graph is weighted and edge_weight is not specified or it is None # raises a GraphEdgeError if the graph is oriented and an edge from second_item to first_item already exists graph.remove_edge(first_item, second_item) # removes the edge from first_item to second_item # if the graph is not directed, this function removes the edge from second_item to first_item too # raises a GraphTypeError if the type of the graph is not None and is different than the type of any of the arguments # raises a GraphElementError if first_item or second_item is not a node that the graph contains # raises a GraphEdgeError if there is no edge from first_item to second_item # the implementation includes an iterator too for node in graph: print(node) # the iterator goes through all nodes in the graph # the __iter__ method actually returns the iterator of the list of nodes of the graph
Project details
Release history Release notifications
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Filename, size  File type  Python version  Upload date  Hashes 

Filename, size pythonicdatastructures0.2.1.tar.gz (27.6 kB)  File type Source  Python version None  Upload date  Hashes View hashes 
Hashes for pythonicdatastructures0.2.1.tar.gz
Algorithm  Hash digest  

SHA256  33003e27471daba887163bec47eba43165b1d7dfaf41310437cbd3c4b0b4eaa7 

MD5  2ac2cc8c92e7edc0986986b8bca6d9d9 

BLAKE2256  57e6d2dc3d7b4973938e0982519dde3c56d1266925f6e0dc1646017af4f1a4eb 