1
##############################################################################
3
# Copyright (c) 2009 Zope Foundation and Contributors.
6
# This software is subject to the provisions of the Zope Public License,
7
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE.
13
##############################################################################
14
"""A sorting mixin class for FieldIndex-like indexes.
16
$Id: sorting.py 100839 2009-06-11 06:15:27Z tseaver $
20
from itertools import islice
22
from zope.interface import implements
23
from zope.index.interfaces import IIndexSort
25
class SortingIndexMixin(object):
27
implements(IIndexSort)
29
_sorting_num_docs_attr = '_num_docs' # Length object
30
_sorting_fwd_index_attr = '_fwd_index' # forward BTree index
31
_sorting_rev_index_attr = '_rev_index' # reverse BTree index
33
def sort(self, docids, reverse=False, limit=None):
34
if (limit is not None) and (limit < 1):
35
raise ValueError('limit value must be 1 or greater')
37
numdocs = getattr(self, self._sorting_num_docs_attr).value
41
if not isinstance(docids,
42
(self.family.IF.Set, self.family.IF.TreeSet)):
43
docids = self.family.IF.Set(docids)
49
fwd_index = getattr(self, self._sorting_fwd_index_attr)
50
rev_index = getattr(self, self._sorting_rev_index_attr)
51
getValue = rev_index.get
54
# use_lazy and use_nbest computations lifted wholesale from
55
# Zope2 catalog without questioning reasoning
56
use_lazy = rlen > numdocs * (rlen / 100 + 1)
57
use_nbest = limit and limit * 4 < rlen
59
# overrides for unit tests
60
if getattr(self, '_use_lazy', False):
62
if getattr(self, '_use_nbest', False):
66
# this is a sort with a limit that appears useful, try to
67
# take advantage of the fact that we can keep a smaller
68
# set of simultaneous values in memory; use generators
69
# and heapq functions to do so.
73
val = getValue(docid, marker)
80
# we use a generator as an iterable in the reverse
81
# sort case because the nlargest implementation does
82
# not manifest the whole thing into memory at once if
84
for val in heapq.nlargest(limit, iterable):
87
# lifted from heapq.nsmallest
89
result = sorted(islice(it, 0, limit))
92
insort = bisect.insort
94
los = result[-1] # los --> Largest of the nsmallest
105
if use_lazy and not reverse:
106
# Since this the sort is not reversed, and the number
107
# of results in the search result set is much larger
108
# than the number of items in this index, we assume it
109
# will be fastest to iterate over all of our forward
110
# BTree's items instead of using a full sort, as our
111
# forward index is already sorted in ascending order
112
# by value. The Zope 2 catalog implementation claims
113
# that this case is rarely exercised in practice.
115
for stored_docids in fwd_index.values():
116
for docid in self.family.IF.intersection(docids,
120
if limit and n >= limit:
123
# If the result set is not much larger than the number
124
# of documents in this index, or if we need to sort in
125
# reverse order, use a non-lazy sort.
127
for docid in sorted(docids, key=getValue, reverse=reverse):
128
if getValue(docid, marker) is not marker:
131
if limit and n >= limit: