Show More
@@ -510,34 +510,178 b' class sortdict(dict):' | |||
|
510 | 510 | self._list.insert(index, key) |
|
511 | 511 | dict.__setitem__(self, key, val) |
|
512 | 512 | |
|
513 | class _lrucachenode(object): | |
|
514 | """A node in a doubly linked list. | |
|
515 | ||
|
516 | Holds a reference to nodes on either side as well as a key-value | |
|
517 | pair for the dictionary entry. | |
|
518 | """ | |
|
519 | __slots__ = ('next', 'prev', 'key', 'value') | |
|
520 | ||
|
521 | def __init__(self): | |
|
522 | self.next = None | |
|
523 | self.prev = None | |
|
524 | ||
|
525 | self.key = _notset | |
|
526 | self.value = None | |
|
527 | ||
|
528 | def markempty(self): | |
|
529 | """Mark the node as emptied.""" | |
|
530 | self.key = _notset | |
|
531 | ||
|
513 | 532 | class lrucachedict(object): |
|
514 | '''cache most recent gets from or sets to this dictionary''' | |
|
515 | def __init__(self, maxsize): | |
|
533 | """Dict that caches most recent accesses and sets. | |
|
534 | ||
|
535 | The dict consists of an actual backing dict - indexed by original | |
|
536 | key - and a doubly linked circular list defining the order of entries in | |
|
537 | the cache. | |
|
538 | ||
|
539 | The head node is the newest entry in the cache. If the cache is full, | |
|
540 | we recycle head.prev and make it the new head. Cache accesses result in | |
|
541 | the node being moved to before the existing head and being marked as the | |
|
542 | new head node. | |
|
543 | ||
|
544 | NOTE: construction of this class doesn't scale well if the cache size | |
|
545 | is in the thousands. Avoid creating hundreds or thousands of instances | |
|
546 | with large capacities. | |
|
547 | """ | |
|
548 | def __init__(self, max): | |
|
516 | 549 | self._cache = {} |
|
517 | self._maxsize = maxsize | |
|
518 | self._order = collections.deque() | |
|
550 | ||
|
551 | self._head = head = _lrucachenode() | |
|
552 | head.prev = head | |
|
553 | head.next = head | |
|
554 | self._size = 1 | |
|
555 | self._capacity = max | |
|
556 | ||
|
557 | def __len__(self): | |
|
558 | return len(self._cache) | |
|
559 | ||
|
560 | def __contains__(self, k): | |
|
561 | return k in self._cache | |
|
519 | 562 | |
|
520 |
def __ |
|
|
521 | value = self._cache[key] | |
|
522 | self._order.remove(key) | |
|
523 | self._order.append(key) | |
|
524 | return value | |
|
563 | def __iter__(self): | |
|
564 | # We don't have to iterate in cache order, but why not. | |
|
565 | n = self._head | |
|
566 | for i in range(len(self._cache)): | |
|
567 | yield n.key | |
|
568 | n = n.next | |
|
569 | ||
|
570 | def __getitem__(self, k): | |
|
571 | node = self._cache[k] | |
|
572 | self._movetohead(node) | |
|
573 | return node.value | |
|
574 | ||
|
575 | def __setitem__(self, k, v): | |
|
576 | node = self._cache.get(k) | |
|
577 | # Replace existing value and mark as newest. | |
|
578 | if node is not None: | |
|
579 | node.value = v | |
|
580 | self._movetohead(node) | |
|
581 | return | |
|
582 | ||
|
583 | if self._size < self._capacity: | |
|
584 | node = self._addcapacity() | |
|
585 | else: | |
|
586 | # Grab the last/oldest item. | |
|
587 | node = self._head.prev | |
|
525 | 588 | |
|
526 | def __setitem__(self, key, value): | |
|
527 |
if key not |
|
|
528 |
|
|
|
529 | del self._cache[self._order.popleft()] | |
|
530 |
|
|
|
531 | self._order.remove(key) | |
|
532 |
self._cache[k |
|
|
533 | self._order.append(key) | |
|
589 | # At capacity. Kill the old entry. | |
|
590 | if node.key is not _notset: | |
|
591 | del self._cache[node.key] | |
|
592 | ||
|
593 | node.key = k | |
|
594 | node.value = v | |
|
595 | self._cache[k] = node | |
|
596 | # And mark it as newest entry. No need to adjust order since it | |
|
597 | # is already self._head.prev. | |
|
598 | self._head = node | |
|
534 | 599 | |
|
535 |
def __ |
|
|
536 |
|
|
|
600 | def __delitem__(self, k): | |
|
601 | node = self._cache.pop(k) | |
|
602 | node.markempty() | |
|
603 | ||
|
604 | # Temporarily mark as newest item before re-adjusting head to make | |
|
605 | # this node the oldest item. | |
|
606 | self._movetohead(node) | |
|
607 | self._head = node.next | |
|
608 | ||
|
609 | # Additional dict methods. | |
|
610 | ||
|
611 | def get(self, k, default=None): | |
|
612 | try: | |
|
613 | return self._cache[k] | |
|
614 | except KeyError: | |
|
615 | return default | |
|
537 | 616 | |
|
538 | 617 | def clear(self): |
|
618 | n = self._head | |
|
619 | while n.key is not _notset: | |
|
620 | n.markempty() | |
|
621 | n = n.next | |
|
622 | ||
|
539 | 623 | self._cache.clear() |
|
540 | self._order = collections.deque() | |
|
624 | ||
|
625 | def _movetohead(self, node): | |
|
626 | """Mark a node as the newest, making it the new head. | |
|
627 | ||
|
628 | When a node is accessed, it becomes the freshest entry in the LRU | |
|
629 | list, which is denoted by self._head. | |
|
630 | ||
|
631 | Visually, let's make ``N`` the new head node (* denotes head): | |
|
632 | ||
|
633 | previous/oldest <-> head <-> next/next newest | |
|
634 | ||
|
635 | ----<->--- A* ---<->----- | |
|
636 | | | | |
|
637 | E <-> D <-> N <-> C <-> B | |
|
638 | ||
|
639 | To: | |
|
640 | ||
|
641 | ----<->--- N* ---<->----- | |
|
642 | | | | |
|
643 | E <-> D <-> C <-> B <-> A | |
|
644 | ||
|
645 | This requires the following moves: | |
|
646 | ||
|
647 | C.next = D (node.prev.next = node.next) | |
|
648 | D.prev = C (node.next.prev = node.prev) | |
|
649 | E.next = N (head.prev.next = node) | |
|
650 | N.prev = E (node.prev = head.prev) | |
|
651 | N.next = A (node.next = head) | |
|
652 | A.prev = N (head.prev = node) | |
|
653 | """ | |
|
654 | head = self._head | |
|
655 | # C.next = D | |
|
656 | node.prev.next = node.next | |
|
657 | # D.prev = C | |
|
658 | node.next.prev = node.prev | |
|
659 | # N.prev = E | |
|
660 | node.prev = head.prev | |
|
661 | # N.next = A | |
|
662 | # It is tempting to do just "head" here, however if node is | |
|
663 | # adjacent to head, this will do bad things. | |
|
664 | node.next = head.prev.next | |
|
665 | # E.next = N | |
|
666 | node.next.prev = node | |
|
667 | # A.prev = N | |
|
668 | node.prev.next = node | |
|
669 | ||
|
670 | self._head = node | |
|
671 | ||
|
672 | def _addcapacity(self): | |
|
673 | """Add a node to the circular linked list. | |
|
674 | ||
|
675 | The new node is inserted before the head node. | |
|
676 | """ | |
|
677 | head = self._head | |
|
678 | node = _lrucachenode() | |
|
679 | head.prev.next = node | |
|
680 | node.prev = head.prev | |
|
681 | node.next = head | |
|
682 | head.prev = node | |
|
683 | self._size += 1 | |
|
684 | return node | |
|
541 | 685 | |
|
542 | 686 | def lrucachefunc(func): |
|
543 | 687 | '''cache most recent results of function calls''' |
@@ -34,5 +34,13 b' def test_lrucachedict():' | |||
|
34 | 34 | d.clear() |
|
35 | 35 | printifpresent(d, ['b', 'c', 'd', 'e', 'f']) |
|
36 | 36 | |
|
37 | # Now test dicts that aren't full. | |
|
38 | d = util.lrucachedict(4) | |
|
39 | d['a'] = 1 | |
|
40 | d['b'] = 2 | |
|
41 | d['a'] | |
|
42 | d['b'] | |
|
43 | printifpresent(d, ['a', 'b']) | |
|
44 | ||
|
37 | 45 | if __name__ == '__main__': |
|
38 | 46 | test_lrucachedict() |
General Comments 0
You need to be logged in to leave comments.
Login now