~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_simple_set_pyx.pyx

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil, Patch Queue Manager, Jelmer Vernooij
  • Date: 2017-01-17 16:20:41 UTC
  • mfrom: (6619.1.2 trunk)
  • Revision ID: tarmac-20170117162041-oo62uk1qsmgc9j31
Merge 2.7 into trunk including fixes for bugs #1622039, #1644003, #1579093 and #1645017. [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2009, 2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Definition of a class that is similar to Set with some small changes."""
 
18
 
 
19
cdef extern from "python-compat.h":
 
20
    pass
 
21
 
 
22
cdef extern from "Python.h":
 
23
    ctypedef unsigned long size_t
 
24
    ctypedef long (*hashfunc)(PyObject*) except -1
 
25
    ctypedef object (*richcmpfunc)(PyObject *, PyObject *, int)
 
26
    ctypedef int (*visitproc)(PyObject *, void *)
 
27
    ctypedef int (*traverseproc)(PyObject *, visitproc, void *)
 
28
    int Py_EQ
 
29
    void Py_INCREF(PyObject *)
 
30
    void Py_DECREF(PyObject *)
 
31
    ctypedef struct PyTypeObject:
 
32
        hashfunc tp_hash
 
33
        richcmpfunc tp_richcompare
 
34
        traverseproc tp_traverse
 
35
 
 
36
    PyTypeObject *Py_TYPE(PyObject *)
 
37
    # Note: *Don't* use hash(), Pyrex 0.9.8.5 thinks it returns an 'int', and
 
38
    #       thus silently truncates to 32-bits on 64-bit machines.
 
39
    long PyObject_Hash(PyObject *) except -1
 
40
        
 
41
    void *PyMem_Malloc(size_t nbytes)
 
42
    void PyMem_Free(void *)
 
43
    void memset(void *, int, size_t)
 
44
 
 
45
 
 
46
# Dummy is an object used to mark nodes that have been deleted. Since
 
47
# collisions require us to move a node to an alternative location, if we just
 
48
# set an entry to NULL on delete, we won't find any relocated nodes.
 
49
# We have to use _dummy_obj because we need to keep a refcount to it, but we
 
50
# also use _dummy as a pointer, because it avoids having to put <PyObject*> all
 
51
# over the code base.
 
52
cdef object _dummy_obj
 
53
cdef PyObject *_dummy
 
54
_dummy_obj = object()
 
55
_dummy = <PyObject *>_dummy_obj
 
56
 
 
57
 
 
58
cdef object _NotImplemented
 
59
_NotImplemented = NotImplemented
 
60
 
 
61
 
 
62
cdef int _is_equal(PyObject *this, long this_hash, PyObject *other) except -1:
 
63
    cdef long other_hash
 
64
 
 
65
    if this == other:
 
66
        return 1
 
67
    other_hash = PyObject_Hash(other)
 
68
    if other_hash != this_hash:
 
69
        return 0
 
70
 
 
71
    # This implements a subset of the PyObject_RichCompareBool functionality.
 
72
    # Namely it:
 
73
    #   1) Doesn't try to do anything with old-style classes
 
74
    #   2) Assumes that both objects have a tp_richcompare implementation, and
 
75
    #      that if that is not enough to compare equal, then they are not
 
76
    #      equal. (It doesn't try to cast them both to some intermediate form
 
77
    #      that would compare equal.)
 
78
    res = Py_TYPE(this).tp_richcompare(this, other, Py_EQ)
 
79
    if res is _NotImplemented:
 
80
        res = Py_TYPE(other).tp_richcompare(other, this, Py_EQ)
 
81
        if res is _NotImplemented:
 
82
            return 0
 
83
    if res:
 
84
        return 1
 
85
    return 0
 
86
 
 
87
 
 
88
cdef public api class SimpleSet [object SimpleSetObject, type SimpleSet_Type]:
 
89
    """This class can be used to track canonical forms for objects.
 
90
 
 
91
    It is similar in function to the interned dictionary that is used by
 
92
    strings. However:
 
93
 
 
94
      1) It assumes that hash(obj) is cheap, so does not need to inline a copy
 
95
         of it
 
96
      2) It only stores one reference to the object, rather than 2 (key vs
 
97
         key:value)
 
98
 
 
99
    As such, it uses 1/3rd the amount of memory to store a pointer to the
 
100
    interned object.
 
101
    """
 
102
    # Attributes are defined in the .pxd file
 
103
    DEF DEFAULT_SIZE=1024
 
104
 
 
105
    def __init__(self):
 
106
        cdef Py_ssize_t size, n_bytes
 
107
 
 
108
        size = DEFAULT_SIZE
 
109
        self._mask = size - 1
 
110
        self._used = 0
 
111
        self._fill = 0
 
112
        n_bytes = sizeof(PyObject*) * size;
 
113
        self._table = <PyObject **>PyMem_Malloc(n_bytes)
 
114
        if self._table == NULL:
 
115
            raise MemoryError()
 
116
        memset(self._table, 0, n_bytes)
 
117
 
 
118
    def __sizeof__(self):
 
119
        # Note: Pyrex doesn't allow sizeof(class) so we re-implement it here.
 
120
        # Bits are:
 
121
        #   1: PyObject
 
122
        #   2: vtable *
 
123
        #   3: 3 Py_ssize_t
 
124
        #   4: PyObject**
 
125
        # Note that we might get alignment, etc, wrong, but at least this is
 
126
        # better than no estimate at all
 
127
        # return sizeof(SimpleSet) + (self._mask + 1) * (sizeof(PyObject*))
 
128
        return (sizeof(PyObject) + sizeof(void*)
 
129
                + 3*sizeof(Py_ssize_t) + sizeof(PyObject**)
 
130
                + (self._mask + 1) * sizeof(PyObject*))
 
131
 
 
132
    def __dealloc__(self):
 
133
        if self._table != NULL:
 
134
            PyMem_Free(self._table)
 
135
            self._table = NULL
 
136
 
 
137
    property used:
 
138
        def __get__(self):
 
139
            return self._used
 
140
 
 
141
    property fill:
 
142
        def __get__(self):
 
143
            return self._fill
 
144
 
 
145
    property mask:
 
146
        def __get__(self):
 
147
            return self._mask
 
148
 
 
149
    def _memory_size(self):
 
150
        """Return the number of bytes of memory consumed by this class."""
 
151
        return sizeof(self) + (sizeof(PyObject*)*(self._mask + 1))
 
152
 
 
153
    def __len__(self):
 
154
        return self._used
 
155
 
 
156
    def _test_lookup(self, key):
 
157
        cdef PyObject **slot
 
158
 
 
159
        slot = _lookup(self, key)
 
160
        if slot[0] == NULL:
 
161
            res = '<null>'
 
162
        elif slot[0] == _dummy:
 
163
            res = '<dummy>'
 
164
        else:
 
165
            res = <object>slot[0]
 
166
        return <int>(slot - self._table), res
 
167
 
 
168
    def __contains__(self, key):
 
169
        """Is key present in this SimpleSet."""
 
170
        cdef PyObject **slot
 
171
 
 
172
        slot = _lookup(self, key)
 
173
        if slot[0] == NULL or slot[0] == _dummy:
 
174
            return False
 
175
        return True
 
176
 
 
177
    cdef PyObject *_get(self, object key) except? NULL:
 
178
        """Return the object (or nothing) define at the given location."""
 
179
        cdef PyObject **slot
 
180
 
 
181
        slot = _lookup(self, key)
 
182
        if slot[0] == NULL or slot[0] == _dummy:
 
183
            return NULL
 
184
        return slot[0]
 
185
 
 
186
    def __getitem__(self, key):
 
187
        """Return a stored item that is equivalent to key."""
 
188
        cdef PyObject *py_val
 
189
 
 
190
        py_val = self._get(key)
 
191
        if py_val == NULL:
 
192
            raise KeyError("Key %s is not present" % key)
 
193
        val = <object>(py_val)
 
194
        return val
 
195
 
 
196
    cdef int _insert_clean(self, PyObject *key) except -1:
 
197
        """Insert a key into self.table.
 
198
 
 
199
        This is only meant to be used during times like '_resize',
 
200
        as it makes a lot of assuptions about keys not already being present,
 
201
        and there being no dummy entries.
 
202
        """
 
203
        cdef size_t i, n_lookup
 
204
        cdef long the_hash
 
205
        cdef PyObject **table, **slot
 
206
        cdef Py_ssize_t mask
 
207
 
 
208
        mask = self._mask
 
209
        table = self._table
 
210
 
 
211
        the_hash = PyObject_Hash(key)
 
212
        i = the_hash
 
213
        for n_lookup from 0 <= n_lookup <= <size_t>mask: # Don't loop forever
 
214
            slot = &table[i & mask]
 
215
            if slot[0] == NULL:
 
216
                slot[0] = key
 
217
                self._fill = self._fill + 1
 
218
                self._used = self._used + 1
 
219
                return 1
 
220
            i = i + 1 + n_lookup
 
221
        raise RuntimeError('ran out of slots.')
 
222
 
 
223
    def _py_resize(self, min_used):
 
224
        """Do not use this directly, it is only exposed for testing."""
 
225
        return self._resize(min_used)
 
226
 
 
227
    cdef Py_ssize_t _resize(self, Py_ssize_t min_used) except -1:
 
228
        """Resize the internal table.
 
229
 
 
230
        The final table will be big enough to hold at least min_used entries.
 
231
        We will copy the data from the existing table over, leaving out dummy
 
232
        entries.
 
233
 
 
234
        :return: The new size of the internal table
 
235
        """
 
236
        cdef Py_ssize_t new_size, n_bytes, remaining
 
237
        cdef PyObject **new_table, **old_table, **slot
 
238
 
 
239
        new_size = DEFAULT_SIZE
 
240
        while new_size <= min_used and new_size > 0:
 
241
            new_size = new_size << 1
 
242
        # We rolled over our signed size field
 
243
        if new_size <= 0:
 
244
            raise MemoryError()
 
245
        # Even if min_used == self._mask + 1, and we aren't changing the actual
 
246
        # size, we will still run the algorithm so that dummy entries are
 
247
        # removed
 
248
        # TODO: Test this
 
249
        # if new_size < self._used:
 
250
        #     raise RuntimeError('cannot shrink SimpleSet to something'
 
251
        #                        ' smaller than the number of used slots.')
 
252
        n_bytes = sizeof(PyObject*) * new_size;
 
253
        new_table = <PyObject **>PyMem_Malloc(n_bytes)
 
254
        if new_table == NULL:
 
255
            raise MemoryError()
 
256
 
 
257
        old_table = self._table
 
258
        self._table = new_table
 
259
        memset(self._table, 0, n_bytes)
 
260
        self._mask = new_size - 1
 
261
        self._used = 0
 
262
        remaining = self._fill
 
263
        self._fill = 0
 
264
 
 
265
        # Moving everything to the other table is refcount neutral, so we don't
 
266
        # worry about it.
 
267
        slot = old_table
 
268
        while remaining > 0:
 
269
            if slot[0] == NULL: # unused slot
 
270
                pass 
 
271
            elif slot[0] == _dummy: # dummy slot
 
272
                remaining = remaining - 1
 
273
            else: # active slot
 
274
                remaining = remaining - 1
 
275
                self._insert_clean(slot[0])
 
276
            slot = slot + 1
 
277
        PyMem_Free(old_table)
 
278
        return new_size
 
279
 
 
280
    def add(self, key):
 
281
        """Similar to set.add(), start tracking this key.
 
282
        
 
283
        There is one small difference, which is that we return the object that
 
284
        is stored at the given location. (which is closer to the
 
285
        dict.setdefault() functionality.)
 
286
        """
 
287
        return self._add(key)
 
288
 
 
289
    cdef object _add(self, key):
 
290
        cdef PyObject **slot, *py_key
 
291
        cdef int added
 
292
 
 
293
        py_key = <PyObject *>key
 
294
        if (Py_TYPE(py_key).tp_richcompare == NULL
 
295
            or Py_TYPE(py_key).tp_hash == NULL):
 
296
            raise TypeError('Types added to SimpleSet must implement'
 
297
                            ' both tp_richcompare and tp_hash')
 
298
        added = 0
 
299
        # We need at least one empty slot
 
300
        assert self._used < self._mask
 
301
        slot = _lookup(self, key)
 
302
        if (slot[0] == NULL):
 
303
            Py_INCREF(py_key)
 
304
            self._fill = self._fill + 1
 
305
            self._used = self._used + 1
 
306
            slot[0] = py_key
 
307
            added = 1
 
308
        elif (slot[0] == _dummy):
 
309
            Py_INCREF(py_key)
 
310
            self._used = self._used + 1
 
311
            slot[0] = py_key
 
312
            added = 1
 
313
        # No else: clause. If _lookup returns a pointer to
 
314
        # a live object, then we already have a value at this location.
 
315
        retval = <object>(slot[0])
 
316
        # PySet and PyDict use a 2-3rds full algorithm, we'll follow suit
 
317
        if added and (self._fill * 3) >= ((self._mask + 1) * 2):
 
318
            # However, we always work for a load factor of 2:1
 
319
            self._resize(self._used * 2)
 
320
        # Even if we resized and ended up moving retval into a different slot,
 
321
        # it is still the value that is held at the slot equivalent to 'key',
 
322
        # so we can still return it
 
323
        return retval
 
324
 
 
325
    def discard(self, key):
 
326
        """Remove key from the set, whether it exists or not.
 
327
 
 
328
        :return: False if the item did not exist, True if it did
 
329
        """
 
330
        if self._discard(key):
 
331
            return True
 
332
        return False
 
333
 
 
334
    cdef int _discard(self, key) except -1:
 
335
        cdef PyObject **slot, *py_key
 
336
 
 
337
        slot = _lookup(self, key)
 
338
        if slot[0] == NULL or slot[0] == _dummy:
 
339
            return 0
 
340
        self._used = self._used - 1
 
341
        Py_DECREF(slot[0])
 
342
        slot[0] = _dummy
 
343
        # PySet uses the heuristic: If more than 1/5 are dummies, then resize
 
344
        #                           them away
 
345
        #   if ((so->_fill - so->_used) * 5 < so->mask)
 
346
        # However, we are planning on using this as an interning structure, in
 
347
        # which we will be putting a lot of objects. And we expect that large
 
348
        # groups of them are going to have the same lifetime.
 
349
        # Dummy entries hurt a little bit because they cause the lookup to keep
 
350
        # searching, but resizing is also rather expensive
 
351
        # For now, we'll just use their algorithm, but we may want to revisit
 
352
        # it
 
353
        if ((self._fill - self._used) * 5 > self._mask):
 
354
            self._resize(self._used * 2)
 
355
        return 1
 
356
 
 
357
    def __iter__(self):
 
358
        return _SimpleSet_iterator(self)
 
359
 
 
360
 
 
361
cdef class _SimpleSet_iterator:
 
362
    """Iterator over the SimpleSet structure."""
 
363
 
 
364
    cdef Py_ssize_t pos
 
365
    cdef SimpleSet set
 
366
    cdef Py_ssize_t _used # track if things have been mutated while iterating
 
367
    cdef Py_ssize_t len # number of entries left
 
368
 
 
369
    def __init__(self, obj):
 
370
        self.set = obj
 
371
        self.pos = 0
 
372
        self._used = self.set._used
 
373
        self.len = self.set._used
 
374
 
 
375
    def __iter__(self):
 
376
        return self
 
377
 
 
378
    def __next__(self):
 
379
        cdef Py_ssize_t mask, i
 
380
        cdef PyObject *key
 
381
 
 
382
        if self.set is None:
 
383
            raise StopIteration
 
384
        if self.set._used != self._used:
 
385
            # Force this exception to continue to be raised
 
386
            self._used = -1
 
387
            raise RuntimeError("Set size changed during iteration")
 
388
        if not SimpleSet_Next(self.set, &self.pos, &key):
 
389
            self.set = None
 
390
            raise StopIteration
 
391
        # we found something
 
392
        the_key = <object>key # INCREF
 
393
        self.len = self.len - 1
 
394
        return the_key
 
395
 
 
396
    def __length_hint__(self):
 
397
        if self.set is not None and self._used == self.set._used:
 
398
            return self.len
 
399
        return 0
 
400
    
 
401
 
 
402
 
 
403
cdef api SimpleSet SimpleSet_New():
 
404
    """Create a new SimpleSet object."""
 
405
    return SimpleSet()
 
406
 
 
407
 
 
408
cdef SimpleSet _check_self(object self):
 
409
    """Check that the parameter is not None.
 
410
 
 
411
    Pyrex/Cython will do type checking, but only to ensure that an object is
 
412
    either the right type or None. You can say "object foo not None" for pure
 
413
    python functions, but not for C functions.
 
414
    So this is just a helper for all the apis that need to do the check.
 
415
    """
 
416
    cdef SimpleSet true_self
 
417
    if self is None:
 
418
        raise TypeError('self must not be None')
 
419
    true_self = self
 
420
    return true_self
 
421
 
 
422
 
 
423
cdef PyObject **_lookup(SimpleSet self, object key) except NULL:
 
424
    """Find the slot where 'key' would fit.
 
425
 
 
426
    This is the same as a dicts 'lookup' function.
 
427
 
 
428
    :param key: An object we are looking up
 
429
    :param hash: The hash for key
 
430
    :return: The location in self.table where key should be put.
 
431
        location == NULL is an exception, but (*location) == NULL just
 
432
        indicates the slot is empty and can be used.
 
433
    """
 
434
    # This uses Quadratic Probing:
 
435
    #  http://en.wikipedia.org/wiki/Quadratic_probing
 
436
    # with c1 = c2 = 1/2
 
437
    # This leads to probe locations at:
 
438
    #  h0 = hash(k1)
 
439
    #  h1 = h0 + 1
 
440
    #  h2 = h0 + 3 = h1 + 1 + 1
 
441
    #  h3 = h0 + 6 = h2 + 1 + 2
 
442
    #  h4 = h0 + 10 = h2 + 1 + 3
 
443
    # Note that all of these are '& mask', but that is computed *after* the
 
444
    # offset.
 
445
    # This differs from the algorithm used by Set and Dict. Which, effectively,
 
446
    # use double-hashing, and a step size that starts large, but dwindles to
 
447
    # stepping one-by-one.
 
448
    # This gives more 'locality' in that if you have a collision at offset X,
 
449
    # the first fallback is X+1, which is fast to check. However, that means
 
450
    # that an object w/ hash X+1 will also check there, and then X+2 next.
 
451
    # However, for objects with differing hashes, their chains are different.
 
452
    # The former checks X, X+1, X+3, ... the latter checks X+1, X+2, X+4, ...
 
453
    # So different hashes diverge quickly.
 
454
    # A bigger problem is that we *only* ever use the lowest bits of the hash
 
455
    # So all integers (x + SIZE*N) will resolve into the same bucket, and all
 
456
    # use the same collision resolution. We may want to try to find a way to
 
457
    # incorporate the upper bits of the hash with quadratic probing. (For
 
458
    # example, X, X+1, X+3+some_upper_bits, X+6+more_upper_bits, etc.)
 
459
    cdef size_t i, n_lookup
 
460
    cdef Py_ssize_t mask
 
461
    cdef long key_hash
 
462
    cdef PyObject **table, **slot, *cur, **free_slot, *py_key
 
463
 
 
464
    py_key = <PyObject *>key
 
465
    # Note: avoid using hash(obj) because of a bug w/ pyrex 0.9.8.5 and 64-bit
 
466
    #       (it treats hash() as returning an 'int' rather than a 'long')
 
467
    key_hash = PyObject_Hash(py_key)
 
468
    i = <size_t>key_hash
 
469
    mask = self._mask
 
470
    table = self._table
 
471
    free_slot = NULL
 
472
    for n_lookup from 0 <= n_lookup <= <size_t>mask: # Don't loop forever
 
473
        slot = &table[i & mask]
 
474
        cur = slot[0]
 
475
        if cur == NULL:
 
476
            # Found a blank spot
 
477
            if free_slot != NULL:
 
478
                # Did we find an earlier _dummy entry?
 
479
                return free_slot
 
480
            else:
 
481
                return slot
 
482
        if cur == py_key:
 
483
            # Found an exact pointer to the key
 
484
            return slot
 
485
        if cur == _dummy:
 
486
            if free_slot == NULL:
 
487
                free_slot = slot
 
488
        elif _is_equal(py_key, key_hash, cur):
 
489
            # Both py_key and cur belong in this slot, return it
 
490
            return slot
 
491
        i = i + 1 + n_lookup
 
492
    raise AssertionError('should never get here')
 
493
 
 
494
 
 
495
cdef api PyObject **_SimpleSet_Lookup(object self, object key) except NULL:
 
496
    """Find the slot where 'key' would fit.
 
497
 
 
498
    This is the same as a dicts 'lookup' function. This is a private
 
499
    api because mutating what you get without maintaing the other invariants
 
500
    is a 'bad thing'.
 
501
 
 
502
    :param key: An object we are looking up
 
503
    :param hash: The hash for key
 
504
    :return: The location in self._table where key should be put
 
505
        should never be NULL, but may reference a NULL (PyObject*)
 
506
    """
 
507
    return _lookup(_check_self(self), key)
 
508
 
 
509
 
 
510
cdef api object SimpleSet_Add(object self, object key):
 
511
    """Add a key to the SimpleSet (set).
 
512
 
 
513
    :param self: The SimpleSet to add the key to.
 
514
    :param key: The key to be added. If the key is already present,
 
515
        self will not be modified
 
516
    :return: The current key stored at the location defined by 'key'.
 
517
        This may be the same object, or it may be an equivalent object.
 
518
        (consider dict.setdefault(key, key))
 
519
    """
 
520
    return _check_self(self)._add(key)
 
521
    
 
522
 
 
523
cdef api int SimpleSet_Contains(object self, object key) except -1:
 
524
    """Is key present in self?"""
 
525
    return (key in _check_self(self))
 
526
 
 
527
 
 
528
cdef api int SimpleSet_Discard(object self, object key) except -1:
 
529
    """Remove the object referenced at location 'key'.
 
530
 
 
531
    :param self: The SimpleSet being modified
 
532
    :param key: The key we are checking on
 
533
    :return: 1 if there was an object present, 0 if there was not, and -1 on
 
534
        error.
 
535
    """
 
536
    return _check_self(self)._discard(key)
 
537
 
 
538
 
 
539
cdef api PyObject *SimpleSet_Get(SimpleSet self, object key) except? NULL:
 
540
    """Get a pointer to the object present at location 'key'.
 
541
 
 
542
    This returns an object which is equal to key which was previously added to
 
543
    self. This returns a borrowed reference, as it may also return NULL if no
 
544
    value is present at that location.
 
545
 
 
546
    :param key: The value we are looking for
 
547
    :return: The object present at that location
 
548
    """
 
549
    return _check_self(self)._get(key)
 
550
 
 
551
 
 
552
cdef api Py_ssize_t SimpleSet_Size(object self) except -1:
 
553
    """Get the number of active entries in 'self'"""
 
554
    return _check_self(self)._used
 
555
 
 
556
 
 
557
cdef api int SimpleSet_Next(object self, Py_ssize_t *pos,
 
558
                            PyObject **key) except -1:
 
559
    """Walk over items in a SimpleSet.
 
560
 
 
561
    :param pos: should be initialized to 0 by the caller, and will be updated
 
562
        by this function
 
563
    :param key: Will return a borrowed reference to key
 
564
    :return: 0 if nothing left, 1 if we are returning a new value
 
565
    """
 
566
    cdef Py_ssize_t i, mask
 
567
    cdef SimpleSet true_self
 
568
    cdef PyObject **table
 
569
    true_self = _check_self(self)
 
570
    i = pos[0]
 
571
    if (i < 0):
 
572
        return 0
 
573
    mask = true_self._mask
 
574
    table= true_self._table
 
575
    while (i <= mask and (table[i] == NULL or table[i] == _dummy)):
 
576
        i = i + 1
 
577
    pos[0] = i + 1
 
578
    if (i > mask):
 
579
        return 0 # All done
 
580
    if (key != NULL):
 
581
        key[0] = table[i]
 
582
    return 1
 
583
 
 
584
 
 
585
cdef int SimpleSet_traverse(SimpleSet self, visitproc visit,
 
586
                            void *arg) except -1:
 
587
    """This is an implementation of 'tp_traverse' that hits the whole table.
 
588
 
 
589
    Cython/Pyrex don't seem to let you define a tp_traverse, and they only
 
590
    define one for you if you have an 'object' attribute. Since they don't
 
591
    support C arrays of objects, we access the PyObject * directly.
 
592
    """
 
593
    cdef Py_ssize_t pos
 
594
    cdef PyObject *next_key
 
595
    cdef int ret
 
596
 
 
597
    pos = 0
 
598
    while SimpleSet_Next(self, &pos, &next_key):
 
599
        ret = visit(next_key, arg)
 
600
        if ret:
 
601
            return ret
 
602
    return 0
 
603
 
 
604
# It is a little bit ugly to do this, but it works, and means that Meliae can
 
605
# dump the total memory consumed by all child objects.
 
606
(<PyTypeObject *>SimpleSet).tp_traverse = <traverseproc>SimpleSet_traverse