keypool 0.1
Generate and maintain a pool of unique integer keys.
License: MIT License
Keywords: data structures

keypool
=======

``keypool`` is a library to generate and maintain a pool of unique
integer keys. Priority is given to reusing freed keys rather than
generating new ones.

This package is meant for situations where keys for a dict are
irrelevant or arbitrary.

Installation
------------

Python
~~~~~~

Only Python 2.6+ is required!

-  Install with pip: ``pip install keypool``
-  Clone and install: ``python python/setup.py install``

If you want to run the tests, ensure ``nose`` is installed with
``pip install nose``.

JavaScript
~~~~~~~~~~

The JavaScript version requires no dependencies or special interpreters.

The basics
----------

Python
~~~~~~

::

          from keypool import KeypoolDict
          items = KeypoolDict()

          # Assign a value with a unique, generated key
          items[items.next()] = 'hello, world'

          # Assign a value but capture the key
          key = items.setitem('hello again, world')

          # Assign anything except an integer, like a normal dict
          items['hello'] = 'world'

When an item is deleted, its key is freed for reuse:

::

          from keypool import KeypoolDict
          items = KeypoolDict()

          # Add some items
          keys = [items.setitem(word) for word in ['aardvark', 'baboon', 'crocodile']]

          # [0, 1, 2]
          print(keys)

          # Delete 0
          del items[keys[0]]

          # Add a new item
          key = items.setitem('dragonfly')

          # 0 (as opposed to 3)
          print(key)             

JavaScript
~~~~~~~~~~

::

         var items = keyPool();
         
         // Assign a value with a unique integer.
         // The key is immediately generated, unlike in Python
         items[items.newKey()] = 'hello, world';
         
         // Assign a value but capture the key
         var key = items.set('hello again, world');
         
         // Assign anything except an integer to the internal object
         items['hello'] = 'world';
              
         items.del(key);      // key 1 is removed and the next free key will be 1
         items.del('hello');  // a key outside the pool is removed

Examples
--------

Let’s say you’re wrapping a timer function in some horrible API:

::

          def timer(unique_name, **kwargs):
                ...

and each active timer needs to be stored for efficient lookup (i.e. a
``dict``). Usually a timestamp or uuid will suffice for this type of
problem:

::

          import time
          
          timers = {}
          
          def create_timer(**kwargs):                  
                key = str(time.time())
                timers[key] = timer(unique_name=key, **kwargs)
                return key

          keys = [create_timer(...) for i in xrange(0, 10)]

Oops, the loop is iterating faster than ``time.time``\ ’s precision and
thus all keys are identical:

::

          # [1310422700.9400001, 1310422700.9400001, 1310422700.9400001, 
          #  1310422700.9400001, 1310422700.9400001, 1310422700.9400001, 
          #  1310422700.9400001, 1310422700.9400001, 1310422700.9400001, 
          #  1310422700.9400001]
          print(keys)
          
          assert not all([keys[0] == key for key in keys])        

A ``KeypoolDict`` solves this problem in a cleaner fashion with unique
integer keys:

::

          from keypool import KeypoolDict
          from operator import delitem
          
          timers = KeypoolDict()
          
          def create_timer(**kwargs):
                key = timers.next()
                timers[key] = timer(unique_name=key, **kwargs)
                return key
          
          keys = [create_timer(...) for i in xrange(0, 10)]

No keys are identical now!

::

          # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
          print(keys)      
          
          assert all([x == y for x,y in zip(sorted(set(keys)), sorted(keys))])

Keys are also reused when deleted, so arbitrarily increasing values are
mostly avoided:

::

          # Delete all the items
          [delitem(timers, key) for key in timers.keys()]
          
          # The old keys are now reused      
          keys = [create_timer(...) for i in xrange(0, 10)]
          
          # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
          print(keys)