1
0

PMap.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  1. #!/usr/bin/env python
  2. # The contents of this file are subject to the BitTorrent Open Source License
  3. # Version 1.1 (the License). You may not copy or use this file, in either
  4. # source code or executable form, except in compliance with the License. You
  5. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  6. #
  7. # Software distributed under the License is distributed on an AS IS basis,
  8. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  9. # for the specific language governing rights and limitations under the
  10. # License.
  11. #
  12. # By David Harrison
  13. # I was playing with doctest when I wrote this. I still haven't
  14. # decided how useful doctest is as opposed to implementing unit tests
  15. # directly. --Dave
  16. # HEREDAVE: Create a bit in the map that is set whenever the
  17. # the map is changed in a way that would invalidate existing iterators?
  18. # Nope. That won't work. How do you know when to reset the bit.
  19. #
  20. # Another way is for the PMap to maintain a set of all valid iterators.
  21. # Whenever an action occurs that invalidates iterators, the set is cleared.
  22. # Before performing any operation on an iterator, the iterator checks
  23. # whether it is in the valid set. For CMap, we could maintain a dead bit
  24. # for all values. When a node is deleted, we set the dead bit.
  25. # Before performing any operation, the iterator checks the dead bit.
  26. from BTL.translation import _
  27. from bisect import bisect_left, bisect_right, insort_left
  28. from copy import copy
  29. # by David Harrison
  30. class PMap(object):
  31. """This is an ordered mapping. PMap --> Python Map, because it is
  32. implemented using dicts and lists.
  33. Unlike a dict, it can be iterated in order and it supports
  34. lower and upper bound searches. It also has an index implemented
  35. with a dict that allows O(1) time lookups based on key.
  36. The in-order mapping is implemented with a Python list. The
  37. index and cross-index are implemented with dicts.
  38. Item insertion: O(n)
  39. Item deletion: O(n)
  40. Key search: O(1)
  41. Value search: n/a
  42. Iteration step: O(1)
  43. This is not semantically equivalent to CMap or CIndexedMap
  44. in the following ways:
  45. - iterators are invalidated by insertions and deletions.
  46. - time complexity is different for many operations.
  47. """
  48. class _AbstractIterator(object):
  49. def __init__(self, map, i = -1 ):
  50. """Creates an iterator pointing to item si in the map.
  51. Do not instantiate directly. Use iterkeys, itervalues, or
  52. iteritems.
  53. Examples of typical behavior:
  54. >>> from PMap import *
  55. >>> m = PMap()
  56. >>> m[12] = 6
  57. >>> m[9] = 4
  58. >>> for k in m:
  59. ... print int(k)
  60. ...
  61. 9
  62. 12
  63. >>>
  64. Example edge cases (empty map):
  65. >>> from PMap import *
  66. >>> m = PMap()
  67. >>> try:
  68. ... i = m.__iter__()
  69. ... i.value()
  70. ... except IndexError:
  71. ... print 'IndexError.'
  72. ...
  73. IndexError.
  74. >>> try:
  75. ... i.next()
  76. ... except StopIteration:
  77. ... print 'stopped'
  78. ...
  79. stopped
  80. @param map: PMap.
  81. @param node: Node that this iterator will point at. If None
  82. then the iterator points to end(). If -1
  83. then the iterator points to one before the beginning.
  84. """
  85. if i == None: self._i = len(map)
  86. else: self._i = i
  87. self._map = map
  88. def __cmp__(self, other):
  89. return self._i - other._i
  90. def key(self):
  91. """@return: the key of the key-value pair referenced by this
  92. iterator.
  93. """
  94. if self._i == -1:
  95. raise IndexError(_("Cannot dereference iterator until after "
  96. "first call to .next."))
  97. return self._map._olist[self._i].k
  98. def value(self):
  99. """@return: the value of the key-value pair currently referenced
  100. by this iterator.
  101. """
  102. if self._i == -1:
  103. raise IndexError(_("Cannot dereference iterator until after "
  104. "first call to next."))
  105. return self._map._olist[self._i].v
  106. def item(self):
  107. """@return the key-value pair referenced by this iterator.
  108. """
  109. return self.key(), self.value()
  110. def _next(self):
  111. self._i += 1
  112. if self._i >= len(self._map):
  113. self._i = len(self._map)
  114. raise StopIteration()
  115. def _prev(self):
  116. self._i -= 1
  117. if self._i <= -1:
  118. self._i = -1
  119. raise StopIteration()
  120. class KeyIterator(_AbstractIterator):
  121. """Returns the next key in the map.
  122. Unlike with CMap, insertion and deletion INVALIDATES iterators.
  123. This is implemented by moving the iterator and then
  124. dereferencing it. If we dereferenced and then moved
  125. then we would get the odd behavior:
  126. Ex: I have keys [1,2,3]. The iterator i points at 1.
  127. print i.next() # prints 1
  128. print i.next() # prints 2
  129. print i.prev() # prints 3
  130. print i.prev() # prints 2
  131. However, because we move and then dereference, when an
  132. iterator is first created it points to nowhere
  133. so that the first next moves to the first element.
  134. Ex:
  135. >>> from PMap import PMap
  136. >>> m = PMap()
  137. >>> m[5] = 1
  138. >>> m[8] = 4
  139. >>> i = m.__iter__()
  140. >>> print int(i.next())
  141. 5
  142. >>> print int(i.next())
  143. 8
  144. >>> print int(i.prev())
  145. 5
  146. We are still left with the odd behavior that an
  147. iterator cannot be dereferenced until after the first next().
  148. Ex edge cases:
  149. >>> from PMap import PMap
  150. >>> m = PMap()
  151. >>> i = m.__iter__()
  152. >>> try:
  153. ... i.prev()
  154. ... except StopIteration:
  155. ... print 'StopIteration'
  156. ...
  157. StopIteration
  158. >>> m[5]='a'
  159. >>> i = m.iterkeys()
  160. >>> int(i.next())
  161. 5
  162. >>> try: i.next()
  163. ... except StopIteration: print 'StopIteration'
  164. ...
  165. StopIteration
  166. >>> int(i.prev())
  167. 5
  168. >>> try: int(i.prev())
  169. ... except StopIteration: print 'StopIteration'
  170. ...
  171. StopIteration
  172. >>> int(i.next())
  173. 5
  174. """
  175. def next(self):
  176. self._next()
  177. return self.key()
  178. def prev(self):
  179. self._prev()
  180. return self.key()
  181. class ValueIterator(_AbstractIterator):
  182. def next(self):
  183. """@return: next value in the map.
  184. >>> from PMap import *
  185. >>> m = PMap()
  186. >>> m[5] = 10
  187. >>> m[6] = 3
  188. >>> i = m.itervalues()
  189. >>> int(i.next())
  190. 10
  191. >>> int(i.next())
  192. 3
  193. """
  194. self._next()
  195. return self.value()
  196. def prev(self):
  197. self._prev()
  198. return self.value()
  199. class ItemIterator(_AbstractIterator):
  200. def next(self):
  201. """@return: next item in the map's key ordering.
  202. >>> from PMap import *
  203. >>> m = PMap()
  204. >>> m[5] = 10
  205. >>> m[6] = 3
  206. >>> i = m.iteritems()
  207. >>> k,v = i.next()
  208. >>> int(k)
  209. 5
  210. >>> int(v)
  211. 10
  212. >>> k,v = i.next()
  213. >>> int(k)
  214. 6
  215. >>> int(v)
  216. 3
  217. """
  218. self._next()
  219. return self.key(), self.value()
  220. def prev(self):
  221. self._prev()
  222. return self.key(), self.value()
  223. class Item(object):
  224. def __init__(self, k, v):
  225. self.k = k
  226. self.v = v
  227. def __cmp__(self, other):
  228. return self.k.__cmp__(other.k)
  229. def __str__(self):
  230. return "Item(%s,%s)" % ( str(self.k), str(self.v) )
  231. def __repr__(self):
  232. return "Item(%s,%s)" % ( str(self.k), str(self.v) )
  233. def __init__(self, d = {}):
  234. """
  235. >>> m = PMap()
  236. >>> len(m)
  237. 0
  238. >>> m[5]=2
  239. >>> len(m)
  240. 1
  241. >>> print m[5]
  242. 2
  243. """
  244. self._olist = [] # list ordered based on key.
  245. self._index = {} # keyed based on key.
  246. for key, value in d.items():
  247. self[key] = value
  248. def __contains__(self,x):
  249. return self.get(x) != None
  250. def __iter__(self):
  251. """@return: KeyIterator positioned one before the beginning of the
  252. key ordering so that the first next() returns the first key."""
  253. return PMap.KeyIterator(self)
  254. def begin(self):
  255. """Returns an iterator pointing at first key-value pair. This
  256. differs from iterkeys, itervalues, and iteritems which return an
  257. iterator pointing one before the first key-value pair.
  258. @return: key iterator to first key-value.
  259. >>> from PMap import *
  260. >>> m = PMap()
  261. >>> m[5.0] = 'a'
  262. >>> i = m.begin()
  263. >>> int(i.key()) # raises no IndexError.
  264. 5
  265. >>> i = m.iterkeys()
  266. >>> try:
  267. ... i.key()
  268. ... except IndexError:
  269. ... print 'IndexError raised'
  270. ...
  271. IndexError raised
  272. """
  273. i = PMap.KeyIterator(self,i=0)
  274. return i
  275. def end(self):
  276. """Returns an iterator pointing after end of key ordering.
  277. The iterator's prev method will move to the last
  278. key-value pair in the ordering. This in keeping with
  279. the notion that a range is specified as [i,j) where
  280. j is not in the range, and the range [i,j) where i==j
  281. is an empty range.
  282. This operation takes O(1) time.
  283. @return: key iterator one after end.
  284. """
  285. i = PMap.KeyIterator(self,None) # None goes to end of map.
  286. return i
  287. def iterkeys(self):
  288. return PMap.KeyIterator(self)
  289. def itervalues(self):
  290. return PMap.ValueIterator(self)
  291. def iteritems(self):
  292. return PMap.ItemIterator(self)
  293. def __len__(self):
  294. return len(self._olist)
  295. def __str__(self):
  296. # dict is not necessarily in order.
  297. #return str(dict(zip(self.keys(),self.values())))
  298. s = "{"
  299. first = True
  300. for k,v in self.items():
  301. if first:
  302. first = False
  303. else:
  304. s += ", "
  305. s += "%d: '%s'" % (k,v)
  306. s += "}"
  307. return s
  308. def __getitem__( self, k ):
  309. return self._index[k]
  310. def __setitem__(self, k, v ):
  311. """O(n) insertion worst case.
  312. >>> from PMap import PMap
  313. >>> m = PMap()
  314. >>> m[6] = 'bar'
  315. >>> m[6]
  316. 'bar'
  317. >>>
  318. """
  319. insort_left(self._olist, PMap.Item(k,v))
  320. self._index[k] = v
  321. def __delitem__(self, k):
  322. """
  323. >>> from CMap import CMap
  324. >>> m = CMap()
  325. >>> m[12] = 'foo'
  326. >>> m[13] = 'bar'
  327. >>> m[14] = 'boo'
  328. >>> del m[12]
  329. >>> try:
  330. ... m[12]
  331. ... except KeyError:
  332. ... print 'ok'
  333. ...
  334. ok
  335. >>> j = m.begin()
  336. >>> int(j.next())
  337. 14
  338. >>> i = m.begin()
  339. >>> i.value()
  340. 'bar'
  341. >>> del m[13] # delete object referenced by an iterator
  342. """
  343. del self._index[k] # raises KeyError if key not in index.
  344. i=bisect_left(self._olist,PMap.Item(k,None))
  345. if self._olist[i].k != k: raise KeyError(k)
  346. del self._olist[i]
  347. def __del__(self):
  348. del self._olist
  349. del self._index
  350. def __repr__(self):
  351. return self.__str__()
  352. def get(self, key, default=None):
  353. """@return value corresponding to specified key or return 'default'
  354. if the key is not found.
  355. """
  356. return self._index.get(key,default)
  357. def keys(self):
  358. """
  359. >>> from PMap import *
  360. >>> m = PMap()
  361. >>> m[4] = 7
  362. >>> m[6] = 3
  363. >>> [int(x) for x in m.keys()] # m.keys() but guaranteed integers.
  364. [4, 6]
  365. """
  366. k = []
  367. for item in self._olist:
  368. k.append(item.k)
  369. return k
  370. def values(self):
  371. """
  372. >>> from PMap import PMap
  373. >>> m = PMap()
  374. >>> m[4] = 7
  375. >>> m[6] = 3
  376. >>> m.values()
  377. [7, 3]
  378. """
  379. v = []
  380. for item in self._olist:
  381. v.append(item.v)
  382. return v
  383. def items(self):
  384. """
  385. >>> from PMap import PMap
  386. >>> m = PMap()
  387. >>> m[4] = 7
  388. >>> m[6] = 3
  389. >>> [(int(x[0]),int(x[1])) for x in m.items()]
  390. [(4, 7), (6, 3)]
  391. """
  392. itms = []
  393. for item in self._olist:
  394. itms.append((item.k,item.v))
  395. return itms
  396. def has_key(self, key):
  397. """
  398. >>> from PMap import PMap
  399. >>> m = PMap()
  400. >>> m[4] = 7
  401. >>> if m.has_key(4): print 'ok'
  402. ...
  403. ok
  404. >>> if not m.has_key(7): print 'ok'
  405. ...
  406. ok
  407. """
  408. return self._index.has_key(key)
  409. def __del__(self):
  410. del self._index
  411. del self._olist
  412. def clear(self):
  413. """delete all entries
  414. >>> from PMap import *
  415. >>> m = PMap()
  416. >>> m[4] = 7
  417. >>> m.clear()
  418. >>> print len(m)
  419. 0
  420. """
  421. self.__del__()
  422. self._olist = []
  423. self._index = {}
  424. def copy(self):
  425. """return shallow copy"""
  426. return PMap(self)
  427. def lower_bound(self,key):
  428. """
  429. Finds smallest key equal to or above the lower bound.
  430. Takes O(log n) time.
  431. @param x: Key of (key, value) pair to be located.
  432. @return: Key Iterator pointing to first item equal to or greater
  433. than key, or end() if no such item exists.
  434. >>> from PMap import PMap
  435. >>> m = PMap()
  436. >>> m[10] = 'foo'
  437. >>> m[15] = 'bar'
  438. >>> i = m.lower_bound(11) # iterator.
  439. >>> int(i.key())
  440. 15
  441. >>> i.value()
  442. 'bar'
  443. Edge cases:
  444. >>> from PMap import PMap
  445. >>> m = PMap()
  446. >>> i = m.lower_bound(11)
  447. >>> if i == m.end(): print 'ok'
  448. ...
  449. ok
  450. >>> m[10] = 'foo'
  451. >>> i = m.lower_bound(11)
  452. >>> if i == m.end(): print 'ok'
  453. ...
  454. ok
  455. >>> i = m.lower_bound(9)
  456. >>> if i == m.begin(): print 'ok'
  457. ...
  458. ok
  459. """
  460. return PMap.KeyIterator(self,bisect_right(self._olist,
  461. PMap.Item(key,None)))
  462. def upper_bound(self, key):
  463. """
  464. Finds largest key equal to or below the upper bound. In keeping
  465. with the [begin,end) convention, the returned iterator
  466. actually points to the key one above the upper bound.
  467. Takes O(log n) time.
  468. @param x: Key of (key, value) pair to be located.
  469. @return: Iterator pointing to first element equal to or greater than
  470. key, or end() if no such item exists.
  471. >>> from PMap import PMap
  472. >>> m = PMap()
  473. >>> m[10] = 'foo'
  474. >>> m[15] = 'bar'
  475. >>> m[17] = 'choo'
  476. >>> i = m.upper_bound(11) # iterator.
  477. >>> i.value()
  478. 'bar'
  479. Edge cases:
  480. >>> from PMap import PMap
  481. >>> m = PMap()
  482. >>> i = m.upper_bound(11)
  483. >>> if i == m.end(): print 'ok'
  484. ...
  485. ok
  486. >>> m[10] = 'foo'
  487. >>> i = m.upper_bound(9)
  488. >>> i.value()
  489. 'foo'
  490. >>> i = m.upper_bound(11)
  491. >>> if i == m.end(): print 'ok'
  492. ...
  493. ok
  494. """
  495. return PMap.KeyIterator(self, bisect_left(self._olist,
  496. PMap.Item(key,None)))
  497. def find(self,key):
  498. """
  499. Finds the item with matching key and returns a KeyIterator
  500. pointing at the item. If no match is found then returns end().
  501. Takes O(log n) time.
  502. >>> from PMap import PMap
  503. >>> m = PMap()
  504. >>> i = m.find(10)
  505. >>> if i == m.end(): print 'ok'
  506. ...
  507. ok
  508. >>> m[10] = 'foo'
  509. >>> i = m.find(10)
  510. >>> int(i.key())
  511. 10
  512. >>> i.value()
  513. 'foo'
  514. """
  515. i = bisect_left(self._olist,PMap.Item(key,None))
  516. if i >= len(self._olist): return self.end()
  517. if self._olist[i].k != key: return self.end()
  518. return PMap.KeyIterator(self,i )
  519. def update_key(self, iter, key):
  520. """
  521. Modifies the key of the item referenced by iter. If the
  522. key change is small enough that no reordering occurs then
  523. this takes amortized O(1) time. If a reordering occurs then
  524. this takes O(log n).
  525. WARNING!!! All iterators including the passed iterator must
  526. be assumed to be invalid upon return. (Note that this would
  527. not be the case with CMap, where update_key would at most
  528. invalidate the passed iterator).
  529. If the passed key is already in the map then this raises
  530. a KeyError exception and the map is left unchanged. If the
  531. iterator is point
  532. Typical use:
  533. >>> from CMap import CMap
  534. >>> m = CMap()
  535. >>> m[10] = 'foo'
  536. >>> m[8] = 'bar'
  537. >>> i = m.find(10)
  538. >>> m.update_key(i,7) # i is assumed to be invalid upon return.
  539. >>> del i
  540. >>> [(int(x[0]),x[1]) for x in m.items()] # reordering occurred.
  541. [(7, 'foo'), (8, 'bar')]
  542. >>> i = m.find(8)
  543. >>> m.update_key(i,9) # no reordering.
  544. >>> del i
  545. >>> [(int(x[0]),x[1]) for x in m.items()]
  546. [(7, 'foo'), (9, 'bar')]
  547. Edge cases:
  548. >>> i = m.find(7)
  549. >>> i.value()
  550. 'foo'
  551. >>> try: # update to key already in the map.
  552. ... m.update_key(i,9)
  553. ... except KeyError:
  554. ... print 'ok'
  555. ...
  556. ok
  557. >>> m[7]
  558. 'foo'
  559. >>> i = m.iterkeys()
  560. >>> try: # updating an iter pointing at BEGIN.
  561. ... m.update_key(i,10)
  562. ... except IndexError:
  563. ... print 'ok'
  564. ...
  565. ok
  566. >>> i = m.end()
  567. >>> try: # updating an iter pointing at end().
  568. ... m.update_key(i,10)
  569. ... except IndexError:
  570. ... print 'ok'
  571. ...
  572. ok
  573. """
  574. old_key = iter.key()
  575. if key == old_key: return
  576. try:
  577. before = copy(iter)
  578. before.prev()
  579. lower = before.key()
  580. except StopIteration:
  581. lower = old_key - 1 # arbitrarily lower.
  582. if lower < key:
  583. try:
  584. iter.next()
  585. higher = i.key()
  586. except StopIteration:
  587. higher = old_key + 1 # arbitrarily higher.
  588. if key < higher: # if no reordering is necessary...
  589. self._olist[iter._i].key = key
  590. del self._index[old_key]
  591. self._index[key] = old_val
  592. return
  593. # else reordering is necessary so delete and reinsert.
  594. del self[old_key]
  595. self[key] = old_val
  596. def append(self, k, v):
  597. """Performs an insertion with the hint that it probably should
  598. go at the end.
  599. Raises KeyError if the key is already in the map.
  600. >>> from PMap import *
  601. >>> m = PMap()
  602. >>> m.append(5,'foo')
  603. >>> m
  604. {5: 'foo'}
  605. >>> m.append(10, 'bar')
  606. >>> m
  607. {5: 'foo', 10: 'bar'}
  608. >>> m.append(3, 'coo') # out-of-order.
  609. >>> m
  610. {3: 'coo', 5: 'foo', 10: 'bar'}
  611. >>> try:
  612. ... m.append(10, 'blah') # append key already in map.
  613. ... except KeyError:
  614. ... print 'ok'
  615. ...
  616. ok
  617. >>> m
  618. {3: 'coo', 5: 'foo', 10: 'bar'}
  619. """
  620. if self._index.has_key(k):
  621. raise KeyError(_("Key is already in the map. "
  622. "Keys must be unique."))
  623. if len(self._olist) == 0 or k > self._olist[len(self._olist)-1].k:
  624. self._olist.append(PMap.Item(k,v))
  625. else:
  626. insort_left(self._olist, PMap.Item(k,v))
  627. self._index[k] = v
  628. class PIndexedMap(PMap):
  629. """This is an ordered mapping, exactly like PMap except that it
  630. provides a cross-index allowing O(1) searches based on value.
  631. This adds the constraint that values must be unique.
  632. The cross-index is implemented with a dict.
  633. Item insertion: O(n)
  634. Item deletion: O(n)
  635. Key search: average O(1)
  636. Value search: average O(1)
  637. Iteration step: O(1)
  638. Memory: O(n)
  639. This is not semantically equivalent to CIndexedMap
  640. in the following ways:
  641. - iterators are invalidated by insertions and deletions.
  642. """
  643. def __init__(self, dict={} ):
  644. """
  645. >>> m = PIndexedMap()
  646. >>> len(m)
  647. 0
  648. >>> m[5]=2
  649. >>> len(m)
  650. 1
  651. >>> print m[5]
  652. 2
  653. """
  654. self._value_index = {} # keyed on value.
  655. PMap.__init__(self, dict)
  656. def __setitem__(self, k, v ):
  657. """O(n) insertion worst case.
  658. >>> from PMap import *
  659. >>> m = PIndexedMap()
  660. >>> m[6] = 'bar'
  661. >>> m[6]
  662. 'bar'
  663. >>> m.get_key_by_value('bar')
  664. 6
  665. >>> try:
  666. ... m[7] = 'bar'
  667. ... except ValueError:
  668. ... print 'value error'
  669. value error
  670. >>> m[6] = 'foo' # change 6 so 7 can be mapped to 'bar'
  671. >>> m[6]
  672. 'foo'
  673. >>> m[7] = 'bar'
  674. >>> m[7]
  675. 'bar'
  676. >>> m[7] = 'bar' # should not raise exception
  677. >>> m[7] = 'goo'
  678. >>> m.get_key_by_value('bar') # should return None.
  679. >>>
  680. """
  681. # if value is already in the map then throw an error.
  682. try:
  683. if self._value_index[v] != k:
  684. raise ValueError( _("Value is already in the map. "
  685. "Both value and key must be unique." ))
  686. except KeyError:
  687. # value was not in the cross index.
  688. pass
  689. try:
  690. old_val = self._index[k]
  691. self._index[k] = v
  692. del self._value_index[old_val]
  693. self._value_index[v] = k
  694. except KeyError:
  695. # key is not already in the map.
  696. pass
  697. insort_left(self._olist, PIndexedMap.Item(k,v))
  698. self._value_index[v] = k
  699. self._index[k] = v
  700. def __delitem__(self, k):
  701. """
  702. >>> from PMap import PIndexedMap
  703. >>> m = PIndexedMap()
  704. >>> m[6] = 'bar'
  705. >>> m[6]
  706. 'bar'
  707. >>> int(m.get_key_by_value('bar'))
  708. 6
  709. >>> del m[6]
  710. >>> if m.get_key_by_value('bar'):
  711. ... print 'found'
  712. ... else:
  713. ... print 'not found.'
  714. not found.
  715. """
  716. del self._index[k] # raises KeyError if key not in index.
  717. i=bisect_left(self._olist,PIndexedMap.Item(k,None))
  718. if self._olist[i].k != k: raise KeyError(k)
  719. v=self._olist[i].v
  720. del self._value_index[v]
  721. del self._olist[i]
  722. def get_key_by_value( self, v ):
  723. """Returns the key cross-indexed from the passed unique value, or
  724. returns None if the value is not in the map."""
  725. k = self._value_index.get(v)
  726. if k == None: return None
  727. return k
  728. def find_key_by_value( self, v ):
  729. """Returns a key iterator cross-indexed from the passed unique value
  730. or end() if no value found.
  731. >>> from PMap import *
  732. >>> m = PIndexedMap()
  733. >>> m[6] = 'abc'
  734. >>> i = m.find_key_by_value('abc')
  735. >>> i.key()
  736. 6
  737. >>> i = m.find_key_by_value('xyz')
  738. >>> if i == m.end(): print 'i points at end()'
  739. i points at end()
  740. """
  741. try:
  742. k = self._value_index[v] # raises KeyError if no value found.
  743. i = bisect_left(self._olist,PIndexedMap.Item(k,None))
  744. return PIndexedMap.KeyIterator(self,i)
  745. except KeyError, e:
  746. return self.end()
  747. def __del__(self):
  748. del self._value_index
  749. PMap.__del__(self)
  750. def clear(self):
  751. """delete all entries
  752. >>> from PMap import PIndexedMap
  753. >>> m = PIndexedMap()
  754. >>> m[4] = 7
  755. >>> m.clear()
  756. >>> print len(m)
  757. 0
  758. """
  759. PMap.clear(self)
  760. self._value_index = {}
  761. def copy(self):
  762. """return shallow copy"""
  763. return PIndexedMap(self)
  764. def update_key(self, iter, key):
  765. """
  766. Modifies the key of the item referenced by iter. If the
  767. key change is small enough that no reordering occurs then
  768. this takes amortized O(1) time. If a reordering occurs then
  769. this takes O(log n).
  770. WARNING!!! The passed iterator MUST be assumed to be invalid
  771. upon return and should be deallocated.
  772. If the passed key is already in the map then this raises
  773. a KeyError exception and the map is left unchanged. If the
  774. iterator is point
  775. Typical use:
  776. >>> from PMap import PIndexedMap
  777. >>> m = PIndexedMap()
  778. >>> m[10] = 'foo'
  779. >>> m[8] = 'bar'
  780. >>> i = m.find(10)
  781. >>> m.update_key(i,7) # i is assumed to be invalid upon return.
  782. >>> del i
  783. >>> m # reordering occurred.
  784. {7: 'foo', 8: 'bar'}
  785. >>> i = m.find(8)
  786. >>> m.update_key(i,9) # no reordering.
  787. >>> del i
  788. >>> m
  789. {7: 'foo', 9: 'bar'}
  790. Edge cases:
  791. >>> i = m.find(7)
  792. >>> i.value()
  793. 'foo'
  794. >>> try: # update to key already in the map.
  795. ... m.update_key(i,9)
  796. ... except KeyError:
  797. ... print 'ok'
  798. ...
  799. ok
  800. >>> m[7]
  801. 'foo'
  802. >>> i = m.iterkeys()
  803. >>> try: # updating an iter pointing at BEGIN.
  804. ... m.update_key(i,10)
  805. ... except IndexError:
  806. ... print 'ok'
  807. ...
  808. ok
  809. >>> i = m.end()
  810. >>> try: # updating an iter pointing at end().
  811. ... m.update_key(i,10)
  812. ... except IndexError:
  813. ... print 'ok'
  814. ...
  815. ok
  816. """
  817. old_key = iter.key()
  818. if key == old_key: return
  819. old_val = iter.value()
  820. if self._index.has_key(key): raise KeyError(key)
  821. try:
  822. before = copy(iter)
  823. before.prev()
  824. lower = before.key()
  825. except StopIteration:
  826. lower = old_key - 1 # arbitrarily lower.
  827. if lower < key:
  828. try:
  829. iter.next()
  830. higher = i.key()
  831. except StopIteration:
  832. higher = old_key + 1 # arbitrarily higher.
  833. if key < higher: # if no reordering is necessary...
  834. self._olist[iter._i].key = key
  835. del self._index[old_key]
  836. self._index[key] = old_val
  837. self._value_index[old_val] = key
  838. return
  839. # else reordering is necessary so delete and reinsert.
  840. del self[old_key]
  841. self[key] = old_val
  842. def append(self, k, v):
  843. """Performs an insertion with the hint that it probably should
  844. go at the end.
  845. Raises KeyError if the key is already in the map.
  846. >>> from PMap import PIndexedMap
  847. >>> m = PIndexedMap()
  848. >>> m.append(5,'foo')
  849. >>> m
  850. {5: 'foo'}
  851. >>> m.append(10, 'bar')
  852. >>> m
  853. {5: 'foo', 10: 'bar'}
  854. >>> m.append(3, 'coo') # out-of-order.
  855. >>> m
  856. {3: 'coo', 5: 'foo', 10: 'bar'}
  857. >>> m.get_key_by_value( 'bar' )
  858. 10
  859. >>> try:
  860. ... m.append(10, 'blah') # append key already in map.
  861. ... except KeyError:
  862. ... print 'ok'
  863. ...
  864. ok
  865. >>> m
  866. {3: 'coo', 5: 'foo', 10: 'bar'}
  867. >>> try:
  868. ... m.append(10, 'coo') # append value already in map.
  869. ... except ValueError:
  870. ... print 'ok'
  871. ...
  872. ok
  873. """
  874. # if value is already in the map then throw an error.
  875. try:
  876. if self._value_index[v] != k:
  877. raise ValueError( _("Value is already in the map. "
  878. "Both values and keys must be unique.") )
  879. except KeyError:
  880. # values was not in the cross index.
  881. pass
  882. if self._index.has_key(k):
  883. raise KeyError( _("Key is already in the map. Both values and "
  884. "keys must be unique.") )
  885. if len(self._olist) == 0 or k > self._olist[len(self._olist)-1].k:
  886. self._olist.append(PIndexedMap.Item(k,v))
  887. else:
  888. insort_left(self._olist, PIndexedMap.Item(k,v))
  889. self._value_index[v] = k
  890. self._index[k] = v
  891. if __name__ == "__main__":
  892. import sys, doctest
  893. ############
  894. # UNIT TESTS
  895. if len(sys.argv) == 1:
  896. import doctest,sys
  897. print "Testing module"
  898. doctest.testmod(sys.modules[__name__])