Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/structure/coerce_dict.pyx
4069 views
1
#*****************************************************************************
2
# Copyright (C) 2007 Robert Bradshaw <[email protected]>
3
#
4
# Distributed under the terms of the GNU General Public License (GPL)
5
#
6
# http://www.gnu.org/licenses/
7
#*****************************************************************************
8
9
10
include "../ext/python_list.pxi"
11
12
13
cdef class TripleDict:
14
"""
15
This is a hashtable specifically designed for (read) speed in
16
the coercion model.
17
18
It differs from a python dict in the following important ways:
19
20
- All keys must be sequence of exactly three elements. All sequence
21
types (tuple, list, etc.) map to the same item.
22
- Comparison is done using the 'is' rather than '==' operator.
23
24
There are special cdef set/get methods for faster access.
25
It is bare-bones in the sense that not all dictionary methods are
26
implemented.
27
28
It is implemented as a list of lists (hereafter called buckets). The bucket
29
is chosen according to a very simple hash based on the object pointer.
30
and each bucket is of the form [k1, k2, k3, value, k1, k2, k3, value, ...]
31
on which a linear search is performed.
32
33
To spread objects evenly, the size should ideally be a prime, and certainly
34
not divisible by 2.
35
36
37
EXAMPLES:
38
39
sage: from sage.structure.coerce_dict import TripleDict
40
sage: L = TripleDict(31)
41
sage: a = 'a'; b = 'b'; c = 'c'
42
sage: L[a,b,c] = 1
43
sage: L[a,b,c]
44
1
45
sage: L[c,b,a] = -1
46
sage: list(L.iteritems()) # random order of output.
47
[(('c', 'b', 'a'), -1), (('a', 'b', 'c'), 1)]
48
sage: del L[a,b,c]
49
sage: list(L.iteritems())
50
[(('c', 'b', 'a'), -1)]
51
sage: len(L)
52
1
53
sage: L.stats() # min, avg, max (bucket length)
54
(0, 0.03225806451612903, 1)
55
sage: L.bucket_lens() # random layout
56
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
57
sage: for i in range(1000):
58
... L[i,i,i] = i
59
sage: len(L)
60
1001
61
sage: L.stats() # random
62
(31, 32.29032258064516, 35)
63
sage: L.bucket_lens() # random layout
64
[33, 34, 32, 32, 35, 32, 31, 33, 34, 31, 32, 34, 32, 31, 31, 32, 32, 31, 31, 33, 32, 32, 32, 33, 33, 33, 31, 33, 33, 32, 31]
65
sage: L = TripleDict(101, L)
66
sage: L.stats() # random
67
(8, 9.9108910891089117, 11)
68
sage: L = TripleDict(3, L)
69
sage: L.stats() # random
70
(291, 333.66666666666669, 410)
71
sage: L[c,b,a]
72
-1
73
sage: L[a,b,c]
74
Traceback (most recent call last):
75
...
76
KeyError: ('a', 'b', 'c')
77
sage: L[a]
78
Traceback (most recent call last):
79
...
80
KeyError: 'a'
81
sage: L[a] = 1
82
Traceback (most recent call last):
83
...
84
KeyError: 'a'
85
86
The following illustrates why even sizes are bad.
87
sage: L = TripleDict(4, L)
88
sage: L.stats()
89
(0, 250.25, 1001)
90
sage: L.bucket_lens()
91
[1001, 0, 0, 0]
92
93
94
AUTHOR:
95
-- Robert Bradshaw, 2007-08
96
"""
97
98
def __init__(self, size, data=None, threshold=0):
99
"""
100
Create a special dict using triples for keys.
101
102
EXAMPLES:
103
sage: from sage.structure.coerce_dict import TripleDict
104
sage: L = TripleDict(31)
105
sage: a = 'a'; b = 'b'; c = 'c'
106
sage: L[a,b,c] = 1
107
sage: L[a,b,c]
108
1
109
"""
110
cdef int i
111
self.threshold = threshold
112
self.buckets = [[] for i from 0 <= i < size]
113
self._size = 0
114
if data is not None:
115
for k, v in data.iteritems():
116
self[k] = v
117
118
def __len__(self):
119
"""
120
The number of items in self.
121
122
EXAMPLES:
123
sage: from sage.structure.coerce_dict import TripleDict
124
sage: L = TripleDict(37)
125
sage: a = 'a'; b = 'b'; c = 'c'
126
sage: L[a,b,c] = 1
127
sage: L[a,b,c] = -1 # re-assign
128
sage: L[a,c,b] = 1
129
sage: L[a,a,None] = None
130
sage: len(L)
131
3
132
"""
133
return self._size
134
135
def stats(self):
136
"""
137
The distribution of items in buckets.
138
139
OUTPUT:
140
(min, avg, max)
141
142
EXAMPLES:
143
sage: from sage.structure.coerce_dict import TripleDict
144
sage: L = TripleDict(37)
145
sage: for i in range(100): L[i,i,i] = None
146
sage: L.stats() # random
147
(2, 2.7027027027027026, 4)
148
149
sage: L = TripleDict(3007)
150
sage: for i in range(100): L[i,i,i] = None
151
sage: L.stats() # random
152
(0, 0.03325573661456601, 1)
153
154
sage: L = TripleDict(1)
155
sage: for i in range(100): L[i,i,i] = None
156
sage: L.stats()
157
(100, 100.0, 100)
158
"""
159
cdef Py_ssize_t size = self._size
160
cdef Py_ssize_t cur, min = size, max = 0
161
for bucket in self.buckets:
162
if bucket:
163
cur = len(bucket)/4
164
if cur < min: min = cur
165
if cur > max: max = cur
166
else:
167
min = 0
168
return min, 1.0*size/len(self.buckets), max
169
170
def bucket_lens(self):
171
"""
172
The distribution of items in buckets.
173
174
OUTPUT:
175
A list of how many items are in each bucket.
176
177
EXAMPLES:
178
sage: from sage.structure.coerce_dict import TripleDict
179
sage: L = TripleDict(37)
180
sage: for i in range(100): L[i,i,i] = None
181
sage: L.bucket_lens() # random
182
[3, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4]
183
sage: sum(L.bucket_lens())
184
100
185
186
sage: L = TripleDict(1)
187
sage: for i in range(100): L[i,i,i] = None
188
sage: L.bucket_lens()
189
[100]
190
"""
191
return [len(self.buckets[i])/4 for i from 0 <= i < len(self.buckets)]
192
193
def _get_buckets(self):
194
"""
195
The actual buckets of self, for debugging.
196
197
EXAMPLE:
198
sage: from sage.structure.coerce_dict import TripleDict
199
sage: L = TripleDict(3)
200
sage: L[0,0,0] = None
201
sage: L._get_buckets() # random
202
[[0, 0, 0, None], [], []]
203
"""
204
return self.buckets
205
206
def __getitem__(self, k):
207
"""
208
EXAMPLES:
209
sage: from sage.structure.coerce_dict import TripleDict
210
sage: L = TripleDict(31)
211
sage: a = 'a'; b = 'b'; c = 'c'
212
sage: L[a,b,c] = 1
213
sage: L[a,b,c]
214
1
215
"""
216
try:
217
k1, k2, k3 = k
218
except (TypeError,ValueError):
219
raise KeyError, k
220
return self.get(k1, k2, k3)
221
222
cdef get(self, k1, k2, k3):
223
cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
224
if h < 0: h = -h
225
cdef Py_ssize_t i
226
bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
227
for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
228
if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
229
PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
230
PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
231
return <object>PyList_GET_ITEM(bucket, i+3)
232
raise KeyError, (k1, k2, k3)
233
234
def __setitem__(self, k, value):
235
"""
236
EXAMPLES:
237
sage: from sage.structure.coerce_dict import TripleDict
238
sage: L = TripleDict(31)
239
sage: a = 'a'; b = 'b'; c = 'c'
240
sage: L[a,b,c] = -1
241
sage: L[a,b,c]
242
-1
243
"""
244
try:
245
k1, k2, k3 = k
246
except (TypeError,ValueError):
247
raise KeyError, k
248
self.set(k1, k2, k3, value)
249
250
cdef set(self, k1, k2, k3, value):
251
if self.threshold and self._size > len(self.buckets) * self.threshold:
252
self.resize()
253
cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
254
if h < 0: h = -h
255
cdef Py_ssize_t i
256
bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
257
for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
258
if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
259
PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
260
PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
261
bucket[i+3] = value
262
return
263
bucket += [k1, k2, k3, value]
264
self._size += 1
265
266
def __delitem__(self, k):
267
"""
268
EXAMPLES:
269
sage: from sage.structure.coerce_dict import TripleDict
270
sage: L = TripleDict(31)
271
sage: a = 'a'; b = 'b'; c = 'c'
272
sage: L[a,b,c] = -1
273
sage: del L[a,b,c]
274
sage: len(L)
275
0
276
"""
277
try:
278
k1, k2, k3 = k
279
except (TypeError,ValueError):
280
raise KeyError, k
281
cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
282
if h < 0: h = -h
283
cdef Py_ssize_t i
284
bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
285
for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
286
if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
287
PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
288
PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
289
del bucket[i:i+4]
290
self._size -= 1
291
return
292
raise KeyError, k
293
294
def resize(self, int buckets=0):
295
"""
296
Changes the number of buckets of self, while preserving the contents.
297
298
If the number of buckets is 0 or not given, it resizes self to the
299
smallest prime that is at least twice as large as self.
300
301
EXAMPLES:
302
sage: from sage.structure.coerce_dict import TripleDict
303
sage: L = TripleDict(8)
304
sage: for i in range(100): L[i,i,i] = None
305
sage: L.bucket_lens() # random
306
[50, 0, 0, 0, 50, 0, 0, 0]
307
sage: L.resize(7) # random
308
[15, 14, 14, 14, 14, 15, 14]
309
sage: L.resize()
310
sage: len(L.bucket_lens())
311
17
312
"""
313
if buckets == 0:
314
buckets = next_odd_prime(2*len(self.buckets))
315
cdef TripleDict new = TripleDict(buckets, self)
316
self.buckets = new.buckets
317
318
def iteritems(self):
319
"""
320
EXAMPLES:
321
sage: from sage.structure.coerce_dict import TripleDict
322
sage: L = TripleDict(31)
323
sage: L[1,2,3] = None
324
sage: list(L.iteritems())
325
[((1, 2, 3), None)]
326
"""
327
return TripleDictIter(self)
328
329
def __reduce__(self):
330
"""
331
Note that we don't expect equality as this class concerns itself with
332
object identity rather than object equality.
333
334
EXAMPLES:
335
sage: from sage.structure.coerce_dict import TripleDict
336
sage: L = TripleDict(31)
337
sage: L[1,2,3] = True
338
sage: loads(dumps(L)) == L
339
False
340
sage: list(loads(dumps(L)).iteritems())
341
[((1, 2, 3), True)]
342
"""
343
return TripleDict, (len(self.buckets), dict(self.iteritems()), self.threshold)
344
345
346
cdef class TripleDictIter:
347
def __init__(self, pairs):
348
"""
349
EXAMPLES:
350
sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
351
sage: L = TripleDict(31)
352
sage: L[1,2,3] = None
353
sage: L.iteritems().next()
354
((1, 2, 3), None)
355
"""
356
self.pairs = pairs
357
self.buckets = iter(self.pairs.buckets)
358
def __iter__(self):
359
"""
360
EXAMPLES:
361
sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
362
sage: L = TripleDict(31)
363
sage: L[1,2,3] = None
364
sage: iter(L.iteritems()).next()
365
((1, 2, 3), None)
366
"""
367
return self
368
def __next__(self):
369
"""
370
EXAMPLES:
371
sage: from sage.structure.coerce_dict import TripleDict, TripleDictIter
372
sage: L = TripleDict(31)
373
sage: L[1,2,3] = None
374
sage: L[3,2,1] = None
375
sage: sorted(L.iteritems())
376
[((1, 2, 3), None), ((3, 2, 1), None)]
377
"""
378
while self.bucket_iter is None:
379
self.bucket_iter = self.buckets.next()
380
self.bucket_iter = iter(self.bucket_iter)
381
try:
382
k1 = self.bucket_iter.next()
383
k2 = self.bucket_iter.next()
384
k3 = self.bucket_iter.next()
385
value = self.bucket_iter.next()
386
return ((k1, k2, k3), value)
387
except StopIteration:
388
self.bucket_iter = None
389
return self.next()
390
391
392
393
cdef long next_odd_prime(long n):
394
if n % 2 == 0:
395
n -= 1
396
cdef long k
397
while n > 0:
398
n += 2
399
k = 3
400
while k*k <= n:
401
if n % k == 0:
402
break
403
k += 2
404
if k*k > n:
405
return n
406
407