keyset.pyx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import logging
  2. from libc.string cimport memcmp, memcpy
  3. from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free
  4. cimport lakesuperior.model.callbacks as cb
  5. from lakesuperior.model.base cimport NULL_TRP, TRP_KLEN, TripleKey
  6. logger = logging.getLogger(__name__)
  7. cdef class Keyset:
  8. """
  9. Pre-allocated array (not set, as the name may suggest) of ``TripleKey``s.
  10. """
  11. def __cinit__(self, size_t capacity=0, expand_ratio=.5):
  12. """
  13. Initialize and allocate memory for the data set.
  14. :param size_t capacity: Number of elements to be accounted for.
  15. """
  16. self.capacity = capacity
  17. self.expand_ratio = expand_ratio
  18. self.data = <TripleKey*>PyMem_Malloc(self.capacity * TRP_KLEN)
  19. if capacity and not self.data:
  20. raise MemoryError('Error allocating Keyset data.')
  21. self._cur = 0
  22. self._free_i = 0
  23. def __dealloc__(self):
  24. """
  25. Free the memory.
  26. This is called when the Python instance is garbage collected, which
  27. makes it handy to safely pass a Keyset instance across functions.
  28. """
  29. PyMem_Free(self.data)
  30. # Access methods.
  31. cdef void seek(self, size_t idx=0):
  32. """
  33. Place the cursor at a certain index, 0 by default.
  34. """
  35. self._cur = idx
  36. cdef size_t size(self):
  37. """
  38. Size of the object as the number of occupied data slots.
  39. Note that this is different from :py:data:`capacity`_, which indicates
  40. the number of allocated items in memory.
  41. """
  42. return self._free_i
  43. cdef size_t tell(self):
  44. """
  45. Tell the position of the cursor in the keyset.
  46. """
  47. return self._cur
  48. cdef bint get_next(self, TripleKey* val):
  49. """
  50. Populate the current value and advance the cursor by 1.
  51. :param void *val: Addres of value returned. It is NULL if
  52. the end of the buffer was reached.
  53. :rtype: bint
  54. :return: True if a value was found, False if the end of the buffer
  55. has been reached.
  56. """
  57. if self._cur >= self._free_i:
  58. return False
  59. val[0] = self.data[self._cur]
  60. self._cur += 1
  61. return True
  62. cdef void add(self, const TripleKey* val, bint check_dup=False) except *:
  63. """
  64. Add a triple key to the array.
  65. """
  66. # Check for deleted triples and optionally duplicates.
  67. if val[0] == NULL_TRP or (check_dup and self.contains(val)):
  68. return
  69. if self._free_i >= self.capacity:
  70. if self.expand_ratio > 0:
  71. # In some edge casees, a very small ratio may round down to a
  72. # zero increase, so the baseline increase is 1 element.
  73. self.resize(1 + <size_t>(self.capacity * (1 + self.expand_ratio)))
  74. else:
  75. raise MemoryError('No space left in key set.')
  76. self.data[self._free_i] = val[0]
  77. self._free_i += 1
  78. cdef void remove(self, const TripleKey* val) except *:
  79. """
  80. Remove a triple key.
  81. This method replaces a triple with NULL_TRP if found. It
  82. does not reclaim space. Therefore, if many removal operations are
  83. forseen, using :py:meth:`subtract`_ is advised.
  84. """
  85. cdef TripleKey* stored_val
  86. self.seek()
  87. while self.get_next(stored_val):
  88. if memcmp(val, stored_val, TRP_KLEN) == 0:
  89. stored_val[0] = NULL_TRP
  90. return
  91. cdef bint contains(self, const TripleKey* val):
  92. """
  93. Whether a value exists in the set.
  94. """
  95. cdef TripleKey stored_val
  96. self.seek()
  97. while self.get_next(&stored_val):
  98. if memcmp(val, stored_val, TRP_KLEN) == 0:
  99. return True
  100. return False
  101. cdef Keyset copy(self):
  102. """
  103. Copy a Keyset.
  104. """
  105. cdef Keyset new_ks = Keyset(self.capacity, expand_ratio=self.expand_ratio)
  106. memcpy(new_ks.data, self.data, self.capacity * TRP_KLEN)
  107. new_ks.seek()
  108. return new_ks
  109. cdef Keyset sparse_copy(self):
  110. """
  111. Copy a Keyset and plug holes.
  112. ``NULL_TRP`` values left from removing triple keys are skipped in the
  113. copy and the set is shrunk to its used size.
  114. """
  115. cdef:
  116. TripleKey val
  117. Keyset new_ks = Keyset(self.capacity, self.expand_ratio)
  118. self.seek()
  119. while self.get_next(&val):
  120. if val != NULL_TRP:
  121. new_ks.add(&val)
  122. new_ks.resize()
  123. return new_ks
  124. cdef void resize(self, size_t size=0) except *:
  125. """
  126. Change the array capacity.
  127. :param size_t size: The new capacity size. If not specified or 0, the
  128. array is shrunk to the last used item. The resulting size
  129. therefore will always be greater than 0. The only exception
  130. to this is if the specified size is 0 and no items have been added
  131. to the array, in which case the array will be effectively shrunk
  132. to 0.
  133. """
  134. if not size:
  135. size = self._free_i
  136. tmp = <TripleKey*>PyMem_Realloc(self.data, size * TRP_KLEN)
  137. if not tmp:
  138. raise MemoryError('Could not reallocate Keyset data.')
  139. self.data = tmp
  140. self.capacity = size
  141. self.seek()
  142. cdef Keyset lookup(self, const Key sk, const Key pk, const Key ok):
  143. """
  144. Look up triple keys.
  145. This works in a similar way that the ``Graph`` and ``LmdbStore``
  146. methods work.
  147. Any and all the terms may be NULL. A NULL term is treated as unbound.
  148. :param const Key* sk: s key pointer.
  149. :param const Key* pk: p key pointer.
  150. :param const Key* ok: o key pointer.
  151. """
  152. cdef:
  153. TripleKey spok
  154. Keyset ret = Keyset(self.capacity)
  155. Key k1, k2
  156. key_cmp_fn_t cmp_fn
  157. if sk and pk and ok: # s p o
  158. pass # TODO
  159. elif sk:
  160. k1 = sk
  161. if pk: # s p ?
  162. k2 = pk
  163. cmp_fn = cb.lookup_skpk_cmp_fn
  164. elif ok: # s ? o
  165. k2 = ok
  166. cmp_fn = cb.lookup_skok_cmp_fn
  167. else: # s ? ?
  168. cmp_fn = cb.lookup_sk_cmp_fn
  169. elif pk:
  170. k1 = pk
  171. if ok: # ? p o
  172. k2 = ok
  173. cmp_fn = cb.lookup_pkok_cmp_fn
  174. else: # ? p ?
  175. cmp_fn = cb.lookup_pk_cmp_fn
  176. elif ok: # ? ? o
  177. k1 = ok
  178. cmp_fn = cb.lookup_ok_cmp_fn
  179. else: # ? ? ?
  180. return self.copy()
  181. self.seek()
  182. while self.get_next(&spok):
  183. if cmp_fn(&spok, k1, k2):
  184. ret.add(&spok)
  185. ret.resize()
  186. return ret
  187. ## Boolean operations.
  188. cdef Keyset merge(Keyset ks1, Keyset ks2):
  189. """
  190. Create a Keyset by merging an``ks2`` Keyset with the current one.
  191. :rtype: Keyset
  192. """
  193. cdef:
  194. TripleKey val
  195. Keyset ks3 = ks1.copy()
  196. ks2.seek()
  197. while ks2.get_next(&val):
  198. ks3.add(&val, True)
  199. ks3.resize()
  200. return ks3
  201. cdef Keyset subtract(Keyset ks1, Keyset ks2):
  202. """
  203. Create a Keyset by subtracting an``ks2`` Keyset from the current one.
  204. :rtype: Keyset
  205. """
  206. cdef:
  207. TripleKey val
  208. Keyset ks3 = Keyset(ks1.capacity)
  209. ks1.seek()
  210. while ks1.get_next(&val):
  211. if val != NULL_TRP and not ks2.contains(&val):
  212. ks3.add(&val)
  213. ks3.resize()
  214. return ks3
  215. cdef Keyset intersect(Keyset ks1, Keyset ks2):
  216. """
  217. Create a Keyset by intersection with an``ks2`` Keyset.
  218. :rtype: Keyset
  219. """
  220. cdef:
  221. TripleKey val
  222. Keyset ks3 = Keyset(ks1.capacity)
  223. ks1.seek()
  224. while ks1.get_next(&val):
  225. if val != NULL_TRP and ks2.contains(&val):
  226. ks3.add(&val)
  227. ks3.resize()
  228. return ks3
  229. cdef Keyset xor(Keyset ks1, Keyset ks2):
  230. """
  231. Create a Keyset by disjunction (XOR) with an``ks2`` Keyset.
  232. :rtype: Keyset
  233. """
  234. cdef:
  235. TripleKey val
  236. Keyset ks3 = Keyset(ks1.capacity + ks2.capacity)
  237. ks1.seek()
  238. while ks1.get_next(&val):
  239. if val != NULL_TRP and not ks2.contains(&val):
  240. ks3.add(&val)
  241. ks2.seek()
  242. while ks2.get_next(&val):
  243. if val != NULL_TRP and not ks1.contains(&val):
  244. ks3.add(&val)
  245. ks3.resize()
  246. return ks3