1
2
3
4 """
5 Data structures: Heaps, lists, queues, and Python hacks.
6 """
7
8 import copy, heapq, itertools, sys, unittest
9 from commons.decs import recursion_guard
12 """Bi-directional dictionary; assumes 1:1 mappings."""
13 - def __init__( self ): self.a2b = {}; self.b2a = {}
14 - def add( self, a, b ): self.a2b[a] = b; self.b2a[b] = a
15 - def get_a( self, a ): return self.a2b[a]
16 - def get_b( self, b ): return self.b2a[b]
21 - def __len__( self ): return len( self.a2b )
27 b = self.a2b.pop(a)
28 del self.b2a[b]
29 return b
31 a = self.b2a.pop(b)
32 del self.a2b[a]
33 return a
34
36 """Case-insensitive string-keyed dictionary."""
43
45 'Mixin for pretty-printing free_structs.'
46
47 @recursion_guard("<...>")
49 attrs = []
50 indentation = " " * nesting
51 for k, v in self.__dict__.iteritems():
52 if not k.startswith("_"):
53 text = [indentation, k, " = "]
54 if isinstance(v, free_struct):
55 text.append(v.__str__(nesting + 1))
56 else:
57 text.append(repr(v))
58 attrs.append("".join(text))
59 attrs.sort()
60 attrs.insert(0, self.__class__.__name__ + ":")
61 return "\n".join(attrs)
62
64 """
65 General-purpose Python object with a bunch of boilerplate utility
66 functionality built-in. Subclass-friendly.
67 """
69 self.__dict__.update( d )
70 self.__dict__.update( args )
71 @recursion_guard("<...>")
73 fields = ( '%s = %r' % ( name, value )
74 for name, value in self.__dict__.iteritems() )
75 return "%s(%s)" % ( self.__class__.__name__, ', '.join( fields ) )
77 return type( self ) == type( other ) and \
78 self.__dict__ == other.__dict__
79 - def __ne__(self, other): return not (self == other)
81 """
82 Return a copy of this but with additional attributes corresponding to
83 the items or attributes from the other operand, depending on if it's a
84 dict or an object, respectively.
85 """
86 dup = copy.copy(self)
87 d = other if isinstance(other, dict) else other.__dict__
88 dup.__dict__.update(d)
89 return dup
91 """
92 Return a copy of this but with additional attributes corresponding to
93 the items or attributes from the other operand, depending on if it's a
94 dict or an object, respectively.
95 """
96 dup = copy.copy(self)
97 d = other if isinstance(other, dict) else other.__dict__
98 dup.__dict__.update(d)
99 return dup
101 """
102 Return a copy of this but with the specified attributes removed.
103 """
104 dup = copy.copy(self)
105 for attr in other: del dup[attr]
106 return dup
107
109 """
110 An object whose attributes do nothing.
111
112 @todo: Unfortunate name; has nothing to do with lazy
113 evaluation. Check where this is used and try to rename it.
114 """
117 - def foo( self, *args ):
119
121 """Create an enumerated type, then add var/value pairs to it.
122 The constructor and the method .ints(names) take a list of variable names,
123 and assign them consecutive integers as values. The method .strs(names)
124 assigns each variable name to itself (that is variable 'v' has value 'v').
125 The method .vals(a=99, b=200) allows you to assign any value to variables.
126 A 'list of variable names' can also be a string, which will be .split().
127 The method .end() returns one more than the maximum int value.
128 Example: opcodes = Enum("add sub load store").vals(illegal=255).
129
130 From U{http://norvig.com/python-iaq.html}.
131
132 @copyright: Peter Norvig"""
133
135
136 - def set(self, var, val):
137 """Set var to the value val in the enum."""
138 if var in vars(self).keys(): raise AttributeError("duplicate var in enum")
139 if val in vars(self).values(): raise ValueError("duplicate value in enum")
140 vars(self)[var] = val
141 return self
142
143 - def strs(self, names):
144 """Set each of the names to itself (as a string) in the enum."""
145 for var in self._parse(names): self.set(var, var)
146 return self
147
148 - def ints(self, names):
149 """Set each of the names to the next highest int in the enum."""
150 for var in self._parse(names): self.set(var, self.end())
151 return self
152
153 - def vals(self, **entries):
154 """Set each of var=val pairs in the enum."""
155 for (var, val) in entries.items(): self.set(var, val)
156 return self
157
159 """One more than the largest int value in the enum, or 0 if none."""
160 try: return max([x for x in vars(self).values() if type(x)==type(0)]) + 1
161 except ValueError: return 0
162
164
165 if type(names) == type(""): return names.split()
166 else: return names
167
170 """
171 Defines all list operations from a small subset of methods.
172
173 Subclasses should define _get_element(i), _set_element(i, value),
174 __len__(), _resize_region(start, end, new_size) and
175 _constructor(iterable). Define __iter__() for extra speed.
176
177 The _get_element() and _set_element() methods are given indices with
178 0 <= i < len(self).
179
180 The _resize_region() method should resize the slice self[start:end]
181 so that it has size new_size. It is given indices such that
182 start <= end, 0 <= start <= len(self) and 0 <= end <= len(self).
183 The resulting elements in self[start:start+new_size] can be set to
184 None or arbitrary Python values.
185
186 The _constructor() method accepts an iterable and should return a
187 new instance of the same class as self, populated with the elements
188 of the given iterable.
189
190 From U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440656}.
191
192 @copyright: Connelly Barns
193 """
195 return cmp(list(self), list(other))
196
198 raise TypeError('list objects are unhashable')
199
201 for i in xrange(len(self)):
202 yield self._get_element(i)
203
205 """
206 Get (start, end, step) tuple from slice object.
207 """
208 (start, end, step) = i.indices(len(self))
209
210 if step == 1:
211 if end < start:
212 end = start
213 step = None
214 if i.step == None:
215 step = None
216 return (start, end, step)
217
219 if i < 0:
220 i += len(self)
221 if i < 0 or i >= len(self):
222 raise IndexError('list index out of range')
223 return i
224
235
237 if isinstance(i, slice):
238 (start, end, step) = self._tuple_from_slice(i)
239 if step != None:
240
241 indices = range(start, end, step)
242 if len(value) != len(indices):
243 raise ValueError(('attempt to assign sequence of size %d' +
244 ' to extended slice of size %d') %
245 (len(value), len(indices)))
246 for (j, assign_val) in enumerate(value):
247 self._set_element(indices[j], assign_val)
248 else:
249
250 if len(value) != (end - start):
251 self._resize_region(start, end, len(value))
252 for (j, assign_val) in enumerate(value):
253 self._set_element(start + j, assign_val)
254 else:
255
256 self._set_element(self._fix_index(i), value)
257
259 if isinstance(i, slice):
260 (start, end, step) = self._tuple_from_slice(i)
261 if step != None:
262
263 indices = range(start, end, step)
264
265 if len(indices) > 0 and indices[0] < indices[-1]:
266 indices.reverse()
267 for j in indices:
268 del self[j]
269 else:
270
271 self._resize_region(start, end, 0)
272 else:
273
274 i = self._fix_index(i)
275 self._resize_region(i, i + 1, 0)
276
278 if isinstance(other, self.__class__):
279 ans = self._constructor(self)
280 ans += other
281 return ans
282 return list(self) + other
283
285 ans = self._constructor(self)
286 ans *= other
287 return ans
288
290 if isinstance(other, self.__class__):
291 ans = other._constructor(self)
292 ans += self
293 return ans
294 return other + list(self)
295
298
300 self[len(self):len(self)] = other
301 return self
302
304 if other <= 0:
305 self[:] = []
306 elif other > 1:
307 aux = list(self)
308 for i in xrange(other-1):
309 self.extend(aux)
310 return self
311
313 self[len(self):len(self)] = [other]
314
316 self[len(self):len(self)] = other
317
319 ans = 0
320 for item in self:
321 if item == other:
322 ans += 1
323 return ans
324
326 for i in xrange(len(self)//2):
327 j = len(self) - 1 - i
328 (self[i], self[j]) = (self[j], self[i])
329
330 - def index(self, x, i=0, j=None):
331 if i != 0 or j is not None:
332 (i, j, ignore) = self._tuple_from_slice(slice(i, j))
333 if j is None:
334 j = len(self)
335 for k in xrange(i, j):
336 if self._get_element(k) == x:
337 return k
338 raise ValueError('index(x): x not in list')
339
342
343 - def pop(self, i=None):
344 if i == None:
345 i = len(self)-1
346 ans = self[i]
347 del self[i]
348 return ans
349
351 for i in xrange(len(self)):
352 if self._get_element(i) == x:
353 del self[i]
354 return
355 raise ValueError('remove(x): x not in list')
356
357
358 if sys.version_info[:3] < (2, 4, 0):
359 - def sort(self, cmpfunc=None):
360 ans = list(self)
361 ans.sort(cmpfunc)
362 self[:] = ans
363 else:
364 - def sort(self, cmpfunc=None, key=None, reverse=False):
365 ans = list(self)
366 if reverse == True:
367 ans.sort(cmpfunc, key, reverse)
368 elif key != None:
369 ans.sort(cmpfunc, key)
370 else:
371 ans.sort(cmpfunc)
372 self[:] = ans
373
376
378 ans = self._constructor([])
379 memo[id(self)] = ans
380 ans[:] = copy.deepcopy(tuple(self), memo)
381 return ans
382
383
384
386 if id(self) in track:
387 return '...'
388 track.append(id(self))
389 ans = '%r' % (list(self),)
390 track.remove(id(self))
391 return ans
392
394 return self.__class__.__name__ + '(' + str(self) + ')'
395
402
405
408
410 assert 0 <= i < len(self)
411 return self.L[i]
412
414 assert 0 <= i < len(self)
415 self.L[i] = x
416
418 assert 0 <= start <= len(self)
419 assert 0 <= end <= len(self)
420 assert start <= end
421 self.L[start:end] = [None] * new_size
422
423
424
425
426
427 -class Heap(ListMixin):
428 """
429 A list that maintains the heap invariant.
430 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440673
431
432 TODO using things like pop() is unsafe! they rely on the
433 unimplemented _get_element()
434
435 TODO things like popmin() produce poor error msgs (when empty)
436 """
437
438 - def __init__(self, iterable=(), key=None):
439 '''
440 @param iterable: An iterable over items to be added to the heap.
441 @param key: Specifies a function of one argument that is used to
442 extract a comparison key from each heap element.
443 '''
444 self._key = key
445 self._lst = []
446 self.extend(iterable)
447 heapq.heapify(self._lst)
448
451
452 - def push(self, item):
453 '''Push the item onto the heap.'''
454 return heapq.heappush(self._lst, self._wrap(item))
455
457 '''Pop the smallest item off the heap'''
458 return self._unwrap(heapq.heappop(self._lst))
459
461 '''Equivalent to "x = heap.popmin(); heap.push(); return x" but more
462 efficient.
463 '''
464 return self._unwrap(heapq.heapreplace(self._lst, self._wrap(item)))
465
467 'Equivalent to "heap.push(); return heap.popmin()" but more efficient.'
468 if self and self[0] < item:
469 return self.replace(item)
470 return item
471
473 '''Return a destructive iterator over the heap's elements.
474
475 Each time next is invoked, it pops the smallest item from the heap.
476 '''
477 while self:
478 yield self.popmin()
479
480
481
483 return self.__class__(iterable, self._key)
484
486 return len(self._lst)
487
489 return self._unwrap(self._lst[pos])
490
492 if isinstance(pos, slice):
493 raise TypeError('Heap objects do no support slice setting')
494 pos = self._fix_index(pos)
495 item = self._wrap(item)
496 lst = self._lst
497 current = lst[pos]
498 lst[pos] = item
499 if item > current:
500 heapq._siftup(lst, pos)
501 if lst[pos] != item:
502 return
503 while pos > 0:
504 parentpos = (pos - 1) >> 1
505 parent = lst[parentpos]
506 if parent <= item:
507 break
508 lst[pos] = parent
509 pos = parentpos
510 lst[pos] = item
511
513 if isinstance(pos, slice):
514 raise TypeError('Heap objects do no support slice deleting')
515 pos = self._fix_index(pos)
516 lst = self._lst
517 if pos == len(lst)-1:
518 del lst[-1]
519 else:
520 self[pos] = self.pop()
521
523 return itertools.imap(self._unwrap, self._lst)
524
526 return bool(self._lst)
527
529 raise TypeError('Heap objects do not support comparison')
530
532 if not isinstance(other,Heap) or len(self) != len(other):
533 return False
534 for i,j in itertools.izip(self,other):
535 if i != j: return False
536 return True
537
539 return not self==other
540
542 return self._lst.count()
543
544 append = push
545
547
548
549 self.push(item)
550
552 if self._key is not None:
553 other = itertools.imap(self._wrap, other)
554 push = heapq.heappush; lst = self._lst
555 for item in other:
556 push(lst,item)
557
559 lst = self._lst; pop = heapq.heappop
560 sorted = []; append = sorted.append
561 while lst:
562 append(pop(lst))
563 self._lst = sorted
564
566 raise TypeError('Heap objects do not support reversal')
567
568
569
571 if self._key is not None:
572 item = (self._key(item),item)
573 return item
574
576 if self._key is not None:
577 item = item[1]
578 return item
579
581 """
582 Given a tree of lists/dicts, perform a deep traversal to transform all the
583 dicts to structs.
584 """
585 if type(x) == dict:
586 return free_struct( ( k, dicts2structs(v) ) for k,v in x.iteritems())
587 elif type(x) == list:
588 return [dicts2structs(v) for v in x]
589 else:
590 return x
591
593 """
594 Given a tree of lists/structs, perform a deep traversal to transform all
595 the structs to dicts.
596 """
597 if type(x) == free_struct:
598 return dict( ( k, structs2dicts(v) ) for k,v in x.__dict__.iteritems() )
599 elif type(x) == list:
600 return [structs2dicts(v) for v in x]
601 else:
602 return x
603
610 dicts = {
611 'atom': 0,
612 'dict': { 'atom': 'atom', 'list': [1,2,3] },
613 'list': [ 'atom', {'key': 'value'} ]
614 }
615
616 structs = dicts2structs(dicts)
617 self.assertEqual(structs.atom, dicts['atom'])
618 self.assertEqual(structs.dict.atom, dicts['dict']['atom'])
619 self.assertEqual(structs.dict.list, dicts['dict']['list'])
620 self.assertEqual(structs.list[0], dicts['list'][0])
621 self.assertEqual(structs.list[1].key, dicts['list'][1]['key'])
622
623 dicts2 = structs2dicts(dicts)
624 self.assertEqual(dicts2['atom'], structs.atom)
625 self.assertEqual(dicts2['dict']['atom'], structs.dict.atom)
626 self.assertEqual(dicts2['dict']['list'], structs.dict.list)
627 self.assertEqual(dicts2['list'][0], structs.list[0])
628 self.assertEqual(dicts2['list'][1]['key'], structs.list[1].key)
629
630 if __name__ == '__main__':
631 unittest.main()
632