Skip to content

Commit

Permalink
Fixed up the TST some more and started URLRouter.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zed A. Shaw committed Sep 20, 2017
1 parent 84460a3 commit dc3adab
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 35 deletions.
23 changes: 16 additions & 7 deletions ex23_ternarytree/test_tstree.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,31 @@ def test_TSTree_get_set():

return urls

def test_TSTree_find_all():
urls = test_TSTree_get_set()
results = [n.value for n in urls.find_all("/k")]
assert results == ["Kiwi", "Kumquat"]

def test_TSTree_find_shortest():
urls = test_TSTree_get_set()
assert urls.find_shortest("/") == "/kiwi/"
assert urls.find_shortest("/k").value == "Kiwi"
assert urls.find_shortest("/kiwiki") == None
assert urls.find_shortest("/a").value == "Apple"
assert urls.find_shortest("/").value == "Kiwi"


def test_TSTree_find_longest():
urls = test_TSTree_get_set()
assert urls.find_longest("/") == "/pineapple/"
assert urls.find_longest("/").value == "Pineapple"


def test_TSTree_find_all():
urls = test_TSTree_get_set()
assert urls.find_all("/k") == ["/kiwi/", "/kumquat/"]

def test_TSTree_find_part():
urls = test_TSTree_get_set()
assert urls.find_part("/") == []
assert urls.find_part("/kaler").value == "Kiwi"
assert urls.find_part("/application").value == "Apple"
assert urls.find_part("/papal").value == "Pineapple"
assert urls.find_part("/apple/120/1000/").value == "Apple"
assert urls.find_part("/kiwi/user/zedshaw/has/stuff").value == "Kiwi"
assert urls.find_part("XTREEME") == None


123 changes: 95 additions & 28 deletions ex23_ternarytree/tstree.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,135 @@
class TSTreeNode(object):

def __init__(self, key, value, low, eq, high):
def __init__(self, char, key, value, low, eq, high):
self.char = char
self.key = key
self.low = low
self.eq = eq
self.high = high
self.value = value

def __repr__(self):
return f"{self.key}:{self.value}<{self.low and self.low.key}={self.eq and self.eq.key}={self.high and self.high.key}>"

class TSTree(object):

def __init__(self):
self.root = None

def _get(self, node, keys):
key = keys[0]
if key < node.key:
return self._get(node.low, keys)
elif key == node.key:
if len(keys) > 1:
return self._get(node.eq, keys[1:])
def _get(self, node, chars):
char = chars[0]
if node == None:
return None
elif char < node.char:
return self._get(node.low, chars)
elif char == node.char:
if len(chars) > 1:
return self._get(node.eq, chars[1:])
else:
return node.value
return node
else:
return self._get(node.high, keys)
return self._get(node.high, chars)

def get(self, key):
keys = [x for x in key]
return self._get(self.root, keys)
chars = [ord(x) for x in key]
node = self._get(self.root, chars)
return node and node.value or None

def _set(self, node, keys, value):
next_key = keys[0]
def _set(self, node, chars, key, value):
next_char = chars[0]

if not node:
# what happens if you add the value here?
node = TSTreeNode(next_key, None, None, None, None)
node = TSTreeNode(next_char, None, None, None, None, None)

if next_key < node.key:
node.low = self._set(node.low, keys, value)
elif next_key == node.key:
if len(keys) > 1:
node.eq = self._set(node.eq, keys[1:], value)
if next_char < node.char:
node.low = self._set(node.low, chars, key, value)
elif next_char == node.char:
if len(chars) > 1:
node.eq = self._set(node.eq, chars[1:], key, value)
else:
# what happens if you DO NOT add the value here?
node.value = value
node.key = key
else:
node.high = self._set(node.high, keys, value)
node.high = self._set(node.high, chars, key, value)

return node

def set(self, key, value):
keys = [x for x in key]
self.root = self._set(self.root, keys, value)
chars = [ord(x) for x in key]
self.root = self._set(self.root, chars, key, value)

def find_shortest(self, key):
pass
nodes = self.find_all(key)
if nodes:
shortest = nodes[0]
for node in nodes:
if len(node.key) < len(shortest.key):
shortest = node
return shortest
else:
return None

def find_longest(self, key):
pass
nodes = self.find_all(key)
longest = nodes[0]
for node in nodes:
if len(node.key) > len(longest.key):
longest = node
return longest

def _find_all(self, node, key, results):
if not node: return

if node.eq == None and node.low == None and node.high == None:
results.append(node)

# if there is a low then go low
if node.low:
self._find_all(node.low, key, results)

if node.eq:
# now follow middle
self._find_all(node.eq, key, results)

if node.high:
# if there is a high then go high
self._find_all(node.high, key, results)


def find_all(self, key):
pass
results = []
chars = [ord(x) for x in key]
start = self._get(self.root, chars)
if start:
self._find_all(start.eq, key, results)
return results

def find_part(self, key):
pass

def find_part(self, key):
"""The difference between part and shortest is this:
If you give find_part a 10 char key, and only 2 chars of the key
match 2 chars in the TSTree, then it will return that key. It is
partial on *both* search key and key/value key.
If you give a 10 char key to shortest, and only 2 chars match then
it doesn't find anything.
"""
# start by just finding the shortest key starting with the first char
found = self.find_shortest(key[:1])
if not found: return None

# now if we found something then keep increasing the key
for i in range(2, len(key)):
stem = key[:i]
node = self.find_shortest(stem)
if not node:
# didn't find something with the new key, so return what
# we've found so far
return found
else:
# found something, so update the best match so far
found = node

return found
46 changes: 46 additions & 0 deletions ex24_urlrouter/test_tstree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from tstree import *


def test_TSTree_get_set():
urls = TSTree()
urls.set("/apple/", "Apple")
urls.set("/grape/", "Grape")
urls.set("/kiwi/", "Kiwi")
urls.set("/kumquat/", "Kumquat")
urls.set("/pineapple/", "Pineapple")

assert urls.get("/apple/") == "Apple"
assert urls.get("/grape/") == "Grape"
assert urls.get("/kiwi/") == "Kiwi"
assert urls.get("/") == None

return urls

def test_TSTree_find_all():
urls = test_TSTree_get_set()
results = [n.value for n in urls.find_all("/k")]
assert results == ["Kiwi", "Kumquat"]

def test_TSTree_find_shortest():
urls = test_TSTree_get_set()
assert urls.find_shortest("/k").value == "Kiwi"
assert urls.find_shortest("/kiwiki") == None
assert urls.find_shortest("/a").value == "Apple"
assert urls.find_shortest("/").value == "Kiwi"


def test_TSTree_find_longest():
urls = test_TSTree_get_set()
assert urls.find_longest("/").value == "Pineapple"


def test_TSTree_find_part():
urls = test_TSTree_get_set()
assert urls.find_part("/kaler").value == "Kiwi"
assert urls.find_part("/application").value == "Apple"
assert urls.find_part("/papal").value == "Pineapple"
assert urls.find_part("/apple/120/1000/").value == "Apple"
assert urls.find_part("/kiwi/user/zedshaw/has/stuff").value == "Kiwi"
assert urls.find_part("XTREEME") == None


135 changes: 135 additions & 0 deletions ex24_urlrouter/tstree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
class TSTreeNode(object):

def __init__(self, char, key, value, low, eq, high):
self.char = char
self.key = key
self.low = low
self.eq = eq
self.high = high
self.value = value

def __repr__(self):
return f"{self.key}:{self.value}<{self.low and self.low.key}={self.eq and self.eq.key}={self.high and self.high.key}>"

class TSTree(object):

def __init__(self):
self.root = None

def _get(self, node, chars):
char = chars[0]
if node == None:
return None
elif char < node.char:
return self._get(node.low, chars)
elif char == node.char:
if len(chars) > 1:
return self._get(node.eq, chars[1:])
else:
return node
else:
return self._get(node.high, chars)

def get(self, key):
chars = [ord(x) for x in key]
node = self._get(self.root, chars)
return node and node.value or None

def _set(self, node, chars, key, value):
next_char = chars[0]

if not node:
# what happens if you add the value here?
node = TSTreeNode(next_char, None, None, None, None, None)

if next_char < node.char:
node.low = self._set(node.low, chars, key, value)
elif next_char == node.char:
if len(chars) > 1:
node.eq = self._set(node.eq, chars[1:], key, value)
else:
# what happens if you DO NOT add the value here?
node.value = value
node.key = key
else:
node.high = self._set(node.high, chars, key, value)

return node

def set(self, key, value):
chars = [ord(x) for x in key]
self.root = self._set(self.root, chars, key, value)

def find_shortest(self, key):
nodes = self.find_all(key)
if nodes:
shortest = nodes[0]
for node in nodes:
if len(node.key) < len(shortest.key):
shortest = node
return shortest
else:
return None

def find_longest(self, key):
nodes = self.find_all(key)
longest = nodes[0]
for node in nodes:
if len(node.key) > len(longest.key):
longest = node
return longest

def _find_all(self, node, key, results):
if not node: return

if node.eq == None and node.low == None and node.high == None:
results.append(node)

# if there is a low then go low
if node.low:
self._find_all(node.low, key, results)

if node.eq:
# now follow middle
self._find_all(node.eq, key, results)

if node.high:
# if there is a high then go high
self._find_all(node.high, key, results)


def find_all(self, key):
results = []
chars = [ord(x) for x in key]
start = self._get(self.root, chars)
if start:
self._find_all(start.eq, key, results)
return results


def find_part(self, key):
"""The difference between part and shortest is this:
If you give find_part a 10 char key, and only 2 chars of the key
match 2 chars in the TSTree, then it will return that key. It is
partial on *both* search key and key/value key.
If you give a 10 char key to shortest, and only 2 chars match then
it doesn't find anything.
"""
# start by just finding the shortest key starting with the first char
found = self.find_shortest(key[:1])
if not found: return None

# now if we found something then keep increasing the key
for i in range(2, len(key)):
stem = key[:i]
node = self.find_shortest(stem)
if not node:
# didn't find something with the new key, so return what
# we've found so far
return found
else:
# found something, so update the best match so far
found = node

return found

0 comments on commit dc3adab

Please sign in to comment.