added index package for multikey dictionaries

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1506 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-11-21 19:52:12 +00:00
parent 589238c76f
commit b49cc08b36
4 changed files with 173 additions and 0 deletions

56
index/README.txt Normal file
View file

@ -0,0 +1,56 @@
========================================
Indexed Collections for Various Purposes
========================================
Multikey Dictionaries
=====================
A MultiKeyDict is a dictionary that expects its keys to be tuples.
>>> from cybertools.index.multikey import MultiKeyDict
>>> registry = MultiKeyDict()
>>> registry[('index.html',)] = 'global index.html'
>>> registry[('index.html',)]
'global index.html'
So this would be nothing special - any dictionary is able to provide this
functionality; but a MultiKeyDict has some fallback mechanisms for retrieving
objects only partly fitting the requested key:
>>> registry.get(('index.html', 'topic', 'zope3', 'Custom'))
'global index.html'
>>> registry[('index.html', 'topic', 'zope3', 'Custom')]
'global index.html'
>>> registry[('index.html', 'topic',)] = 'index.html for type "topic"'
>>> registry[('index.html', 'topic', 'zope3', 'Custom')]
'index.html for type "topic"'
It is also possible to keep intermediate parts of a key variable by
setting them to None:
>>> registry[('index.html', None, None, 'Custom')] = 'Global index.html for Custom skin'
The more on the left a matching key part is the higher is its priority:
>>> registry[('index.html', 'topic', 'zope3', 'Custom')]
'index.html for type "topic"'
>>> registry[('index.html', 'task', 'bugfixes', 'Custom')]
'Global index.html for Custom skin'
>>> registry[('edit.html', 'topic', 'zope3', 'Custom')] = 'very special edit.html'
>>> registry[('index.html', 'task', 'bugfixes', 'Custom')]
'Global index.html for Custom skin'
>>> registry[('index.html', 'topic', 'zope3', 'Custom')]
'index.html for type "topic"'
>>> registry.get(('edit.html', 'task', 'bugfixes', 'Custom'))

3
index/__init__.py Normal file
View file

@ -0,0 +1,3 @@
"""
$Id$
"""

84
index/multikey.py Normal file
View file

@ -0,0 +1,84 @@
#
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Dictionaries with multiple keys.
$Id$
"""
_not_found = object()
_default = object()
class MultiKeyDict(dict):
def __init__(self, **kw):
super(MultiKeyDict, self).__init__(**kw)
self.singleKeyDict = {}
def __setitem__(self, key, value):
assert type(key) is tuple
super(MultiKeyDict, self).__setitem__(key, value)
for n, k in enumerate(key):
if k:
entry = self.singleKeyDict.setdefault((n, k), [])
if value not in entry:
entry.append((key, value))
def __getitem__(self, key):
r = self.get(key, _default)
if r is _default:
raise KeyError(key)
return r
def get(self, key, default=None):
assert type(key) is tuple
kl = list(key)
while kl:
r = super(MultiKeyDict, self).get(tuple(kl), _not_found)
if r is not _not_found:
return r
kl.pop()
return default
def get(self, key, default=None):
assert type(key) is tuple
firstTry = super(MultiKeyDict, self).get(key, _not_found)
# fast return for full match:
if firstTry is not _not_found:
return firstTry
collector = {}
for n, k in enumerate(key):
rList = self.singleKeyDict.get((n, k), [])
for r in rList:
skip = False
for nx, kx in enumerate(r[0]):
if kx and kx != key[nx]: # if stored key elements are present
skip = True # they must match
break
if skip:
continue
entry = collector.setdefault(r[1], [])
entry.append(n)
if not collector:
return default
#print 'collector', collector
results = sorted((-len(value), value, o) for o, value in collector.items())
#print 'sorted', results
return results[0][2]

30
index/tests.py Executable file
View file

@ -0,0 +1,30 @@
#! /usr/bin/python
"""
Tests for the 'cybertools.index' package.
$Id$
"""
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from cybertools.index import multikey
class Test(unittest.TestCase):
"Basic tests for the index package."
def testBasicStuff(self):
pass
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
return unittest.TestSuite((
DocFileSuite('README.txt', optionflags=flags),
unittest.makeSuite(Test),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')