This is a new section dedicated to smallish snippets of code I've written that I am particularly fond of for one reason or another.
Recursion & Generators
This is the description of the problem.
# Given a data structure like this:
x = {
'lion':['a','b'],
'tiger':['m','o'],
'cheetah':['y','z'],
}
# I want to end up with this:
y = [
{ 'lion':'a', 'tiger':'m', 'cheetah':'y', },
{ 'lion':'a', 'tiger':'m', 'cheetah':'z', },
{ 'lion':'a', 'tiger':'o', 'cheetah':'y', },
{ 'lion':'a', 'tiger':'o', 'cheetah':'z', },
{ 'lion':'b', 'tiger':'m', 'cheetah':'y', },
{ 'lion':'b', 'tiger':'m', 'cheetah':'z', },
{ 'lion':'b', 'tiger':'o', 'cheetah':'y', },
{ 'lion':'b', 'tiger':'o', 'cheetah':'z', },
]
And here is what I came up with. I really like how generators and recursion play well together. Makes for an elegant solution IMO.
def combinator(items):
expanded = [[(key, v) for v in values] for (key, values) in items]
return (dict(l) for l in _citer(expanded))
def _citer(lst):
if lst:
(head, tail) = (lst[0], lst[1:])
if not tail:
for t in head:
yield [t]
else:
for t in head:
for h in _citer(tail):
yield [t] + h
if __name__=="__main__":
from pprint import pprint
val = combinator(x.items())
pprint(val)
Trie
This is an version of a Trie I whipped up for a bot agent-string matching code that used the xml database from user-agents.org to match any bot. Below is just the Trie part with some simple test input at the end so you can run it and 'see' it in action.
#!/usr/bin/python
class Trie(object):
""" Tree/graph structure that supports prefix matchin lookups.
"""
def __init__(self):
self.root = [None, {}]
self.processed = []
self.last = None
def add(self, key, value):
""" Add the given value for the given key.
"""
node = self.root
key = key.lower()
ch = key[0]
if ch not in self.processed:
processed = self.processed
if self.last: self.pack(self.last)
processed.append(ch)
self.last = ch
elif ch != self.last:
raise RuntimeError("Unsorted agent string: %s" % key)
for ch in key:
node = node[1].setdefault(ch, [None, {}])
node[0] = value
def find(self, key):
""" Return the value for the given key or None.
"""
node = self.root
for ch in key.lower():
if ch in node[1]:
node = node[1][ch]
elif 'x' in node[1]:
# source uses 'x' as wildcard
node = node[1]['x']
elif node[0]:
return node[0]
else:
return None
return node[0]
def pack(self, start=None):
""" memory efficiency packing.
"""
if start:
prune(self.root[1][start])
else:
prune(self.root)
def find_prefix(self, key):
""" Find longest prefix-key available.
Find as much of the key as one can, by using the longest
prefix that has a value. Return (value, remainder) where
remainder is the rest of the given string.
"""
node = self.root
remainder = key
for ch in key:
try:
node = node[1][ch]
except KeyError:
return (node[0], remainder)
remainder = remainder[1:]
return (node[0], remainder)
def _prune(node, point, prune_points):
""" Recusive prunning call.
node is recursive current node
point is last >2 branch point
prune_points is result list of all found points
"""
prune_children = False
if len(node[1]) > 1:
prune_children = True
if node[0] and point:
prune_points.append((point, node))
for n in node[1].values():
if prune_children:
_prune(n, n, prune_points)
else:
_prune(n, point, prune_points)
def prune(node):
""" Given a node, prunes unncessary suffix's from all subnodes.
Works by recursing depth first through the tree tracking the
last point where there was more than one branch. It prunes
the tree one place below that.
"""
pp = []
_prune(node, None, pp)
for (p, end) in pp:
(p[0], p[1]) = end
if __name__ == "__main__":
t = Trie()
t.add("fobar", "1")
t.add("fozar", "2")
t.add("azar", "3")
from copy import deepcopy
rc = deepcopy(t.root)
print t.root
prune(t.root[1]['f'])
print
print t.root
print
print rc == t.root