-
Notifications
You must be signed in to change notification settings - Fork 234
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed up the TST some more and started URLRouter.
- Loading branch information
Zed A. Shaw
committed
Sep 20, 2017
1 parent
84460a3
commit dc3adab
Showing
4 changed files
with
292 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |