Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add implementation for dictionaries #34

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ So far, the seealgo library provides visualization for the following data struct
- List: `append(value)`, `insert(index, value)`, `remove(value)`, `__setitem__(index, value)`
- Binary Search Tree (provided as a nested dictionary): `insert(child)`, `remove(value)`
- Set: `add(value)`, `remove(value)`, `clear()`, `update(values)`
- Dictionary (nested and non-nested): `update(iterable)`, `pop(key)`

## Installation
This library requires you to have `graphviz` installed on your system using the [instructions](https://graphviz.org/download/) appropriate for your system, or by using the following command if you are on a macOS device:
Expand All @@ -27,6 +28,8 @@ pip install seealgo
```

## Using seealgo
(for examples of all data structures available with seealgo, visit our [GitHub Pages](https://sarahtang7.github.io/seealgo/)!)

This is an example of using the List module to visualize appending 5 to a list:

```python
Expand Down
12 changes: 12 additions & 0 deletions outputFiles/initDictOutput.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
digraph Dict {
rankdir=LR
subgraph cluster_Dict {
node [color=white style=filled]
label=Dictionary
style=filled
color=lightgrey
key1 [label="key1: one"]
key2 [label="key2: 2"]
key3 [label="key3: three"]
}
}
15 changes: 15 additions & 0 deletions outputFiles/nestedDictOutput.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
digraph Dict {
rankdir=LR
subgraph cluster_Dict {
node [color=white style=filled]
label=Dictionary
style=filled
color=lightgrey
key1 [label="key1: value1"]
key2 [label=key2 color=lightblue2 shape=rectangle style=filled]
nested_key1 [label="nested_key1: nested_value1"]
key2 -> nested_key1
nested_key2 [label="nested_key2: nested_value2"]
key2 -> nested_key2
}
}
13 changes: 13 additions & 0 deletions outputFiles/popDictOutput1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
digraph Dict {
rankdir=LR
subgraph cluster_Dict {
node [color=white style=filled]
label=Dictionary
style=filled
color=lightgrey
apple [label="apple: red"]
banana [label="banana: yellow"]
goldfish [label="goldfish: gold"]
kiwi [label="kiwi: green"]
}
}
12 changes: 12 additions & 0 deletions outputFiles/popDictOutput2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
digraph Dict {
rankdir=LR
subgraph cluster_Dict {
node [color=white style=filled]
label=Dictionary
style=filled
color=lightgrey
apple [label="apple: red"]
banana [label="banana: yellow"]
kiwi [label="kiwi: green"]
}
}
13 changes: 13 additions & 0 deletions outputFiles/updateDictOutput1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
digraph Dict {
rankdir=LR
subgraph cluster_Dict {
node [color=white style=filled]
label=Dictionary
style=filled
color=lightgrey
1 [label="1: 1"]
2 [label="2: 4"]
3 [label="3: 9"]
4 [label="4: 16"]
}
}
16 changes: 16 additions & 0 deletions outputFiles/updateDictOutput2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
digraph Dict {
rankdir=LR
subgraph cluster_Dict {
node [color=white style=filled]
label=Dictionary
style=filled
color=lightgrey
1 [label="1: 1"]
2 [label="2: 4"]
3 [label="3: 9"]
4 [label="4: 16"]
5 [label="5: 25" color=green style=filled]
6 [label="6: 36" color=green style=filled]
7 [label="7: 49" color=green style=filled]
}
}
4 changes: 3 additions & 1 deletion seealgo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

from seealgo.see_tree_algo import Tree
from seealgo.see_set_algo import Set
from seealgo.see_dict_algo import Dict
from .see_list_algo import List

myList = List()
myTree = Tree()
mySet = Set()
myDict = Dict()

__all__ = [
'List', 'Tree', 'Set'
'List', 'Tree', 'Set', 'Dict'
]
121 changes: 121 additions & 0 deletions seealgo/see_dict_algo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""

This module contains the functionality to visualize a dictionary
data structure as it changes throughout a function.

This involves the following classes:

* `TrackedDict(dict)`: detects changes made to a given dictionary
data structure and triggers creation of a new visualization for each change.

* `Dict`: uses graphviz to construct a visualization of the dictionary using a table.

"""

from graphviz import Digraph

class TrackedDict(dict):
"""
Tracks changes to a dictionary data structure and triggers creation of new visualization

Args:
dict: the dictionary data structure to track
"""

def update(self, iterable):
"""
Sets the value of a key-value pair and checks the keys of the dictionary

Args:
key (Any): key of the key-value pair to be set
value (Any): value to be set for the specified key
"""
super().update(iterable)
keys = list(iterable.keys())
Dict.create_viz(self, self, keys)


def pop(self, key):
"""
Removes a key-value pair given the key.

Args:
key (Any): key of the key-value pair to be removed

Raises:
KeyError: If the element to be removed is not in the dictionary.
"""
super().pop(key)
Dict.create_viz(self, self)


class Dict:
"""
Create graphviz visualization for dictionary data structure
"""

filenum = 1

def see(self, func, data):
"""
Creates a visualization for the initial dictionary and starts tracking
a given dictionary as it changes throughout a given function.

Args:
func (function): function that the dictionary is being altered through
data (dict): dictionary to track
"""
Dict.create_viz(self, data)
data = TrackedDict(data)
if func is not None:
func(data)


def create_viz(self, data, keys=None):
"""
Creates and renders a visualization of the dictionary using graphviz

Args:
data (dict): dictionary that is being visualized
key (iterable): optional list of keys of new key-value pairs
"""

if keys is None:
keys = {}

di_graph = Digraph('Dict', filename=f'dict{Dict.filenum}.gv')
Dict.filenum += 1
di_graph.attr(rankdir='LR')

with di_graph.subgraph(name='cluster_Dict') as subgraph:
subgraph.attr(label='Dictionary')
subgraph.attr(style='filled')
subgraph.attr(color='lightgrey')
subgraph.node_attr.update(style='filled', color='white')

# Create nodes for each key-value pair in the dictionary
for k, val in data.items():
if isinstance(val, dict): # if value is a nested dictionary
subgraph.node(str(k), label=str(k), shape='rectangle',
style='filled', color='lightblue2')

for nested_k, nested_v in val.items():
if nested_k in keys:
subgraph.node(str(nested_k), label=f"{str(nested_k)}: {str(nested_v)}",
style='filled', color='green')

else:
subgraph.node(str(nested_k), label=f"{str(nested_k)}: {str(nested_v)}")
# Add an edge from parent node to child node
subgraph.edge(str(k), str(nested_k))

else: # if value is not a nested dictionary

if k in keys:
subgraph.node(str(k), label=f"{str(k)}: {str(val)}",
style='filled', color='green')
else:
subgraph.node(str(k), label=f"{str(k)}: {str(val)}")

# Render the graph to a file
di_graph.view()
110 changes: 110 additions & 0 deletions seealgo/tests/test_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""
This file contains unit tests for
visualizations involving dictionary data structures.
"""

from seealgo import Dict


### UNIT TESTS


def test_initdict():
"""
Test the visualization of an unchanged dictionary.
"""

init_dict = {'key1': 'one', 'key2': 2, 'key3': 'three'}
viz0 = Dict()
viz0.see(None, init_dict)

with open('dict1.gv', 'r', encoding='utf-8') as file:
viz_contents = file.read()

with open('outputFiles/initDictOutput.txt', 'r', encoding='utf-8') as true_file:
true_contents = true_file.read()

assert viz_contents == true_contents


def test_nesteddict():
"""
Test the visualization of a nested dictionary.
"""

nested_dict = {
'key1': 'value1',
'key2': {
'nested_key1': 'nested_value1',
'nested_key2': 'nested_value2'
}
}
viz_nested = Dict()
viz_nested.see(None, nested_dict)

with open('dict2.gv', 'r', encoding='utf-8') as file:
viz_contents = file.read()

with open('outputFiles/nestedDictOutput.txt', 'r', encoding='utf-8') as true_file:
true_contents = true_file.read()

assert viz_contents == true_contents


def test_updatedict():
"""
Test the visualization of adding
a key:value pair to a dictionary.
"""

dict1 = {'1': 1, '2': 4, '3': 9, '4':16}
dict_new = {'5': 25, '6': 36, '7': 49}
viz1 = Dict()

def update_func(user_dict):
user_dict.update(dict_new)
return user_dict

viz1.see(update_func, dict1)

with open('dict3.gv', 'r', encoding='utf-8') as file:
viz_contents1 = file.read()
with open('dict4.gv', 'r', encoding='utf-8') as file:
viz_contents2 = file.read()

with open('outputFiles/updateDictOutput1.txt', 'r', encoding='utf-8') as true_file:
true_contents1 = true_file.read()
with open('outputFiles/updateDictOutput2.txt', 'r', encoding='utf-8') as true_file:
true_contents2 = true_file.read()

assert viz_contents1 == true_contents1
assert viz_contents2 == true_contents2


def test_popfromdict():
"""
Test the visualization of removing
a value from a dictionary.
"""

dict2 = {'apple': 'red', 'banana': 'yellow', 'goldfish': 'gold', 'kiwi': 'green'}
viz2 = Dict()

def pop_func(user_dict):
user_dict.pop('goldfish')
return user_dict

viz2.see(pop_func, dict2)

with open('dict5.gv', 'r', encoding='utf-8') as file:
viz_contents1 = file.read()
with open('dict6.gv', 'r', encoding='utf-8') as file:
viz_contents2 = file.read()

with open('outputFiles/popDictOutput1.txt', 'r', encoding='utf-8') as true_file:
true_contents1 = true_file.read()
with open('outputFiles/popDictOutput2.txt', 'r', encoding='utf-8') as true_file:
true_contents2 = true_file.read()

assert viz_contents1 == true_contents1
assert viz_contents2 == true_contents2