Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

root/tags/1.4.0/containers.py

Revision 116 (checked in by fumanchu, 3 years ago)

New __all__ module attributes.

  • Property svn:eol-style set to native
Line 
1 """Useful container classes.
2
3 According to Stroustrup:
4 (http://www.research.att.com/~bs/glossary.html#Gcontainer)
5
6 container - (1) object that holds other objects. (2) type of object that
7 holds other objects. (3) template that generates types of objects that
8 hold other objects. (4) standard library template such as vector, list,
9 and map.
10
11 Find herein the containers: warehouse and Graph.
12
13 This work, including the source code, documentation
14 and related data, is placed into the public domain.
15
16 The orginal author is Robert Brewer, Amor Ministries.
17
18 THIS SOFTWARE IS PROVIDED AS-IS, WITHOUT WARRANTY
19 OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF
20 MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE
21 ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE
22 RESULTING FROM THE USE, MODIFICATION, OR
23 REDISTRIBUTION OF THIS SOFTWARE.
24
25 """
26
27 __all__ = ["Graph", "warehouse"]
28
29
30 def warehouse(stock, factory=None):
31     """warehouse(stock, factory=None) -> iavailable, iremainder.
32     
33     Iterate over stock, extending as needed. Once the 'stock' sequence is
34     exhausted, the factory function is called to produce a new valid object
35     upon each subsequent call to next().
36     
37     If factory is None, the class of the first item in the sequence is used
38     as a constructor. Otherwise, the factory function does not receive any
39     arguments, so if your class has mandatory arguments to __init__,
40     wrap the class in a function which can supply those.
41     
42     The most common use for warehouse is to reuse a set of existing
43     objects, often because object creation and/or destruction is expensive.
44     
45     Example:
46     
47     available, remainder = warehouse(seq)
48     for line in order:
49         line.use(available.next())
50     for item in remainder:
51         item.close()
52     """
53     stock = iter(stock)
54    
55     def pull():
56         for item in stock:
57             yield item
58        
59         if factory is None:
60             try:
61                 local_factory = item.__class__
62             except NameError:
63                 raise ValueError("Empty sequence and no factory supplied.")
64         else:
65             local_factory = factory
66        
67         while True:
68             yield local_factory()
69    
70     return pull(), stock
71
72
73 class Graph(dict):
74     """An unweighted graph. Can be directed or undirected."""
75    
76     def __init__(self, data={}, directed=False):
77         self.update(data)
78         self.directed = directed
79         self._cached_paths = {}
80         self.declared_paths = {}
81    
82     def add(self, node):
83         if node not in self:
84             self[node] = []
85             self._cached_paths = self.declared_paths.copy()
86    
87     def _make_arc(self, node, othernode):
88         bucket = self.setdefault(node, [])
89         if othernode not in bucket:
90             bucket.append(othernode)
91    
92     def connect(self, node, othernodes):
93         """Create an edge between node and each node in othernodes.
94         
95         Examples:
96         Graph().connect('A', ('B', 'C', 'D')) becomes:
97               --B
98              /
99             A---C
100              \
101               --D
102         Graph(directed=True).connect('A', ('B', 'C', 'D')) becomes:
103               ->B
104              /
105             A-->C
106              \
107               ->D
108         """
109         try:
110             othernodes = iter(othernodes)
111         except TypeError:
112             othernodes = (othernodes, )
113         for othernode in othernodes:
114             self._make_arc(node, othernode)
115             if not self.directed:
116                 self._make_arc(othernode, node)
117         self._cached_paths = self.declared_paths.copy()
118    
119     def chain(self, *nodes):
120         """Create an edge between each node in sequence.
121         
122         Examples:
123         Graph().chain('A', 'B', 'C', 'D') becomes:
124             A--B--C--D
125         Graph(directed=True).chain('A', 'B', 'C', 'D') becomes:
126             A-->B-->C-->D
127         """
128         node = nodes[0]
129         for nextnode in nodes[1:]:
130             self._make_arc(node, nextnode)
131             if not self.directed:
132                 self._make_arc(nextnode, node)
133             # Lather, rinse, repeat
134             node = nextnode
135         self._cached_paths = self.declared_paths.copy()
136    
137     def shortest_path(self, start, end):
138         """A list of nodes, or None if start is valid but no path is found."""
139         key = (start, end)
140         try:
141             return self._cached_paths[key][:]
142         except KeyError:
143             shortest = self._shortest_path(start, end)
144             self._cached_paths[key] = shortest
145             if shortest is not None:
146                 shortest = shortest[:]
147             return shortest
148    
149     def _shortest_path(self, start, end, path=[]):
150         """A list of nodes, or None if start is valid but no path is found."""
151         if path == []:
152             # Test for presence of start in self.
153             # Let the KeyError propagate out.
154             nodes = self[start]
155             path = [start]
156         else:
157             path = path + [start]
158             if start == end:
159                 return path
160             nodes = self[start]
161        
162         shortest = None
163         for node in nodes:
164             if node not in path:
165                 try:
166                     newpath = self._shortest_path(node, end, path)
167                 except KeyError:
168                     pass
169                 else:
170                     if newpath:
171                         if not shortest or len(newpath) < len(shortest):
172                             shortest = newpath
173         return shortest
174    
175     def declare_path(self, path, symmetric=True):
176         start, end = path[0], path[-1]
177         self.declared_paths[(start, end)] = path[:]
178         self._cached_paths[(start, end)] = path[:]
179         if symmetric:
180             revpath = path[:]
181             revpath.reverse()
182             self.declared_paths[(end, start)] = revpath
183             self._cached_paths[(end, start)] = revpath[:]
184
185
Note: See TracBrowser for help on using the browser.