Skip to content

Commit

Permalink
Merge pull request #11 from cshaley/fixed_ids
Browse files Browse the repository at this point in the history
Add node indexing functionality and fix ids to location in tree
  • Loading branch information
joowani committed Nov 10, 2017
2 parents b0c8683 + 043e5d3 commit 6c5979d
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 10 deletions.
139 changes: 137 additions & 2 deletions binarytree/__init__.py
Expand Up @@ -42,6 +42,12 @@ def __setattr__(self, name, value):
value.parent = self
object.__setattr__(self, name, value)

def __getitem__(self, index):
return _get_node_with_id(self, index)

def __setitem__(self, index, value):
return _set_node_with_id(self, index, value)

def convert(self):
return convert(self)

Expand All @@ -60,6 +66,15 @@ def is_root(self):
def is_leaf(self):
return self.right is _null and self.left is _null

def is_child_of(self, parent):
return self.parent is parent

def is_left_child_of(self, parent):
return self is _left_of(parent)

def is_right_child_of(self, parent):
return self is _right_of(parent)

def leafs(self, values_only=False):
return leafs(self, values_only)

Expand Down Expand Up @@ -390,14 +405,14 @@ def _inject_ids(root):
left_child_copy = _copy_with_id(left_child, id_counter)
_set_left(node_copy, left_child_copy)
next_copies.append(left_child_copy)
id_counter += 1
id_counter += 1

if right_child != _null:
next_nodes.append(right_child)
right_child_copy = _copy_with_id(right_child, id_counter)
_set_right(node_copy, right_child_copy)
next_copies.append(right_child_copy)
id_counter += 1
id_counter += 1
index += 1

current_nodes = next_nodes
Expand Down Expand Up @@ -451,6 +466,126 @@ def _generate_values(height, multiplier=1):
return sample(range(count * multiplier), count)


def _get_node_with_id(root, index):
"""Calculate index in the tree relative to the root node
:param root: The root node of the tree
:type: Node
:param index: index of the node to replace
:type: Int
"""
root = _prepare_tree(root)
if not isinstance(index, int) or index < 0:
raise ValueError("Requested id must be a non-negative integer.")
current_nodes = [root]
current_id = -1
current_index = 0

while current_nodes and current_id < index:
next_nodes = []
current_index = 0

while current_index < len(current_nodes) and current_id < index:
node = current_nodes[current_index]
if node is not _null:
left_child = _left_of(node)
right_child = _right_of(node)
else:
left_child = _null
right_child = _null

if left_child != _null:
next_nodes.append(left_child)
else:
next_nodes.append(_null)
if right_child != _null:
next_nodes.append(right_child)
else:
next_nodes.append(_null)
current_index += 1
current_id += 1
current_nodes = next_nodes

if node is _null:
raise IndexError("Requested node id not present in tree.")
return node


def _get_parent_of_node_with_id(root, index):
"""Get the index in the tree of the parent of a node.
Used for setting new nodes with the setitem method.
:param root: The root node of the tree
:type: Node
:param index: index of the node whose parent you're looking for
:type: Int
"""
root = _prepare_tree(root)
current_nodes = [root]
current_id = 0
current_index = 0

while current_nodes and current_id <= index:
next_nodes = []
current_index = 0

while current_index < len(current_nodes) and current_id <= index:
node = current_nodes[current_index]
if _is_node(node):
left_child = _left_of(node)
right_child = _right_of(node)
else:
left_child = _null
right_child = _null

if left_child != _null:
next_nodes.append(left_child)
elif _is_node(node):
next_nodes.append((current_id, 'left'))
else:
next_nodes.append(_null)
if right_child != _null:
next_nodes.append(right_child)
elif _is_node(node):
next_nodes.append((current_id, 'right'))
else:
next_nodes.append(_null)
current_index += 1
current_id += 1
current_nodes = next_nodes

if node is _null:
raise IndexError("Requested node id's parent not present in tree.")
elif _is_node(node):
return _parent_of(node), 'right' if node.is_right_child_of(_parent_of(node)) else 'left'
else:
return _get_node_with_id(root, node[0]), node[1]


def _set_node_with_id(root, index, value):
"""Set the node with the given id with the passed in value
:param root: The root node of the tree
:type: Node
:param index: index of the node to replace
:type: Int
:param value: New node to replace the node at the given index
:type: Node
"""
if not _is_node(value):
raise ValueError("Value must be of type Node.")
if not isinstance(index, int) or index < 0:
raise ValueError("Index must be a positive integer.")
elif index == 0:
raise ValueError("Cannot replace root node of tree. "
"Index must be a positive integer.")
parent, left_or_right = _get_parent_of_node_with_id(root, index)
if left_or_right is 'right':
_set_right(parent, value)
else:
_set_left(parent, value)


def customize(node_class,
node_init,
null_value,
Expand Down
61 changes: 53 additions & 8 deletions tests.py
Expand Up @@ -84,6 +84,14 @@ def test_node():
assert node.left.right.is_leaf() is True
assert node.is_leaf() is False

assert node.left.is_child_of(node) is True
assert node.right.is_child_of(node) is True
assert node.is_child_of(node) is False
assert node.left.is_left_child_of(node) is True
assert node.right.is_left_child_of(node) is False
assert node.right.is_right_child_of(node) is True
assert node.left.is_right_child_of(node) is False

assert node.level() == 0
assert node.right.level() == 1

Expand Down Expand Up @@ -290,6 +298,43 @@ def test_convert():
assert attr(bt, 'right.right') is None


def test_setitem():
node = Node(1)
node.left = Node(2)
node.right = Node(3)
with pytest.raises(ValueError):
node[2] = 5
with pytest.raises(ValueError):
node[0] = Node(1)
with pytest.raises(ValueError):
node[-1] = Node(1)
with pytest.raises(ValueError):
node[3.14] = Node(1)
node[1] = Node(11)
node[2] = Node(12)
node[3] = Node(13)
node[4] = Node(15)
with pytest.raises(IndexError):
node[12] = Node(17)


def test_getitem():
node = Node(1)
node.left = Node(2)
node.right = Node(3)
assert node[0] == node
assert node[1] == node.left
assert node[2] == node.right
with pytest.raises(ValueError):
node[-1]
with pytest.raises(IndexError):
node[3]
with pytest.raises(ValueError):
node[3.14]
with pytest.raises(ValueError):
node[None]


def test_get_levels():
for invalid_argument in [None, 1, 'foo']:
with pytest.raises(ValueError) as err:
Expand Down Expand Up @@ -587,7 +632,7 @@ def convert_self_show_ids(target):
assert output == ['',
'0 ',
' \\ ',
' 1',
' 2',
' '
]

Expand All @@ -608,7 +653,7 @@ def convert_self_show_ids(target):
' / \\ ',
'1 2',
' \\ ',
' 3 ',
' 4 ',
' '
]
with CaptureOutput() as output:
Expand All @@ -618,7 +663,7 @@ def convert_self_show_ids(target):
' / \\ ',
'1 2',
' \\ / ',
' 3 4 ',
' 4 5 ',
' '
]
with CaptureOutput() as output:
Expand All @@ -628,7 +673,7 @@ def convert_self_show_ids(target):
' / \\ ',
'1 2 ',
' \\ / \\ ',
' 3 4 5',
' 4 5 6',
' '
]
with CaptureOutput() as output:
Expand Down Expand Up @@ -681,7 +726,7 @@ def convert_self_show_all(target):
assert output == ['',
'0:1_ ',
' \\ ',
' 1:3',
' 2:3',
' '
]

Expand All @@ -701,7 +746,7 @@ def convert_self_show_all(target):
' / \\ ',
'1:2_ 2:3',
' \\ ',
' 3:5 ',
' 4:5 ',
' '
]

Expand All @@ -712,7 +757,7 @@ def convert_self_show_all(target):
' / \\ ',
'1:2_ _2:3',
' \\ / ',
' 3:5 4:6 ',
' 4:5 5:6 ',
' '
]
with CaptureOutput() as output:
Expand All @@ -722,7 +767,7 @@ def convert_self_show_all(target):
' / \\ ',
'1:2_ _2:3_ ',
' \\ / \\ ',
' 3:5 4:6 5:7',
' 4:5 5:6 6:7',
' '
]
with CaptureOutput() as output:
Expand Down

0 comments on commit 6c5979d

Please sign in to comment.