LordKey

Detect the sequence element by identifier or the identifier by element of sequence.


License
MIT
Install
pip install LordKey==0.0.3

Documentation

LordKey

It solves the problem of determining the combination in a sequence based on the some alphabet with a given length, and use combination to determine the iteration index.

The problem.

There are several elements to iterate - a, b and c. How many possible combinations for unique enumeration if key size is 3? Or which combination is at the tenth iteration? Or which iteration corresponds the acc combination?

For abc alphabet and 3 key size can be created the next iterations:

 0. aaa      1. aab      2. aac      3. aba      4. abb      5. abc
 6. aca      7. acb      8. acc      9. baa     10. bab     11. bac
12. bba     13. bbb     14. bbc     15. bca     16. bcb     17. bcc
18. caa     19. cab     20. cac     21. cba     22. cbb     23. cbc
24. cca     25. ccb     26. ccc

So, the maximum number of iterations - 27, for 10 iteration corresponds to baa combination and the acc combination - it is 7 iteration.

The theory.

Use the arbitrary alphabet (set of available characters to create a key) and size of key can be created a sequence of unique combinations, where each new combination has its unique numeric index (from 0 to N - where the N is maximum number of possible combinations or infinity).

If specify the index (for example an ID in the table of database) - will be returned the combination (key) for this index, and if specify combination - will be returned it index.

P.s. The sequence is not created - just calculate the data of a specified element. This algorithm allows you to quickly get the result.

Example:

# INITIAL DATA
# =================
# Alphabet | abcde
# ---------+-------
# Key size | 3
#
# SEQUENCE
# ==============================================
# ID  |  0  |  1  |  2  | ... | 122 | 123 | 124
# ----+-----+-----+-----+-----+-----+-----+-----
# Key | aaa | aab | aac | ... | eec | eed | eee

lk = LordKey(alphabet='abcde', size=3)
lk.get_key_by_id(122) # eec
lk.get_id_by_key('ecc') # 122

The alphabet may be omitted then will be used value by default. If not set the size value - key size can be from one char to infinity.

Example:

# Size not specified.
lk = LordKey(alphabet='abc')
lk.get_key_by_id(1) # b
lk.get_key_by_id(10) # bab
lk.get_key_by_id(100) # bacab
lk.get_key_by_id(1000) # bbabaab
lk.get_key_by_id(10000) # bbbcabbab
lk.get_key_by_id(100000) # bcaacabbcab
lk.get_key_by_id(1000000) # bcbccbacacaab
lk.get_key_by_id(10000000) # caacbbaabbacbab

Cache

The LordKey class can be expanded by adding caching capabilities. To do this, you must override a some of built-in methods:

  • _cache_init(self, *args, **kwargs) - to initialize the cache settings.
  • _cache_write(self, *, id, key) - to write the id and the corresponding sequence into cache.
  • _cache_read(self, *, id=None, key=None) - to read data from the cache. Can specified only id or just key at an one moment of the time.
  • _cache_is_active(self) - returns True if the cache is enabled.

Example - cache as dict on the class level.

See an example in the examples/classcache.py.

from lordkey import LordKey


class LordKeyClassCache(LordKey):
    """Support the cache at the class level."""
    # Cache to accelerate the implementation of the same operations.
    __GLOBAL_CLOUD = dict()

    def __init__(self, alphabet=None, size=None, cache=False):
        """Advanced constructor - added the ability to manage the cache."""
        super().__init__(alphabet=alphabet, size=size)
        self._cache_init(cache=cache)

    def _cache_init(self, *args, **kwargs):
        """Create a data structure of cache."""
        # Cache cloud.
        self._cache, self._cloud = kwargs.get('cache', False), None
        if self._cache:
            # Since the cache is implemented at the class level is necessary
            # to ensure that the different cache to be used for different
            # settings.
            section = '{}:{}'.format(self._alphabet, self._size)
            if section not in self.__GLOBAL_CLOUD:
                CloudEntry = collections.namedtuple('CloudEntry', 'id key')
                self.__GLOBAL_CLOUD[section] = CloudEntry(dict(), dict())

            self._cloud = self.__GLOBAL_CLOUD[section]

    def _cache_is_active(self):
        """Returns True if the cache is activated."""
        return self._cache

    def _cache_write(self, *, id, key):
        """Add to cache."""
        self._cloud.id[id] = key
        self._cloud.key[key] = id

Example - cache as dict on the object level.

See an example in the examples/objectcache.py.

from lordkey import LordKey


class LordKeyObjectCache(LordKey):
    """Support the cache at the class level."""

    def __init__(self, alphabet=None, size=None, cache=False):
        """Advanced constructor - added the ability to manage the cache."""
        super().__init__(alphabet=alphabet, size=size)
        self._cache_init(cache=cache)

    def _cache_init(self, *args, **kwargs):
        """Create a data structure of cache."""
        # Cache cloud.
        self._cache, self._cloud = kwargs.get('cache', False), None
        if self._cache:
            CloudEntry = collections.namedtuple('CloudEntry', 'id key')
            self._cloud = CloudEntry(dict(), dict())

    def _cache_is_active(self):
        """Returns True if the cache is activated."""
        return self._cache

    def _cache_write(self, *, id, key):
        """Add to cache."""
        self._cloud.id[id] = key
        self._cloud.key[key] = id

    def _cache_read(self, *, id=None, key=None):
        """Read from cache."""
        if id is not None:
            return self._cloud.id.get(id, None)
        elif key is not None:
            return self._cloud.key.get(key, None)

Example - cache in a REDIS.

See an example in the examples/rediscache.py.

from lordkey import LordKey


# Redis settings.
REDIS_POLL = redis.ConnectionPool(host='localhost', port=6379, db=0)


class LordKeyRedisCache(LordKey):
    """Support the cache use Redis."""

    def __init__(self, alphabet=None, size=None, cache=False):
        """Advanced constructor - added the ability to manage the cache."""
        super().__init__(alphabet, size)
        self._cache_init(cache=cache)

    def _cache_init(self, *args, **kwargs):
        """..."""
        """Create a data structure of cache."""
        self._cache = kwargs.get('cache', False)
        self._cloud = self._dbkey = self._redis_cli = None

        if self._cache:
            self._redis_cli = redis.StrictRedis(connection_pool=REDIS_POLL)
            self._dbkey = '{}:{}'.format(self._alphabet, self._size)

            cloud = self._redis_cli.get(self._dbkey)
            if not cloud:
                CloudEntry = json.dumps({'id': {}, 'key': {}})
                self._redis_cli.set(self._dbkey, CloudEntry)
                cloud = self._redis_cli.get(self._dbkey)

            # We use the virtual cache at the object level.
            self._cloud = json.loads(cloud.decode('utf-8'))

    def _cache_is_active(self):
        """Returns True if the cache is activated."""
        return self._cache

    def _cache_write(self, *, id, key):
        """Add to cache."""
        # Attention.
        # If each time to add the JSON data in Redis - it slows down the
        # algorithm.
        self._cloud['id'][str(id)] = key
        self._cloud['key'][key] = id

    def _cache_read(self, *, id=None, key=None):
        """Read from cache."""
        if id is not None:
            return self._cloud['id'].get(str(id), None)
        elif key is not None:
            return self._cloud['key'].get(key, None)

    def cache_save(self):
        """Write virtual cache into Redis."""
        if not self._redis_cli:
            return

        self._redis_cli.set(self._dbkey, json.dumps(self._cloud))

Best algorithm for caching.

For different tasks need to use different caching algorithms. You can experiment to see different speed caching algorithms in different tasks. See an example in the examples/speedtest.py.

The test results for different tasks:

If object is created and used repeatedly.
1. Object cache:         1.109303779026959
2. Class cache:          1.1138295580167323
3. Redis cache:          2.4269069299916737
4. Origin:               2.758108651963994
If for each new test used a new object.
1. Class cache:          1.1082238990347832
2. Object cache:         2.0088599160080776
3. Origin:               2.589507105993107
4. Redis cache:          3.3289793099975213
If use single call.
1. Class cache:          0.3119982249918394
2. Origin:               0.3376156840240583
3. Redis cache:          3.0777672510012053
4. Object cache:         16.18929815205047