gooderp18绿色标准版
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

560 line
20KB

  1. from __future__ import annotations
  2. import ast
  3. from abc import ABC, abstractmethod
  4. import typing
  5. if typing.TYPE_CHECKING:
  6. from collections.abc import Collection, Iterable
  7. class SetDefinitions:
  8. """ A collection of set definitions, where each set is defined by an id, a
  9. name, its supersets, and the sets that are disjoint with it. This object
  10. is used as a factory to create set expressions, which are combinations of
  11. named sets with union, intersection and complement.
  12. """
  13. __slots__ = ('__leaves',)
  14. def __init__(self, definitions: dict[int, dict]):
  15. """ Initialize the object with ``definitions``, a dict which maps each
  16. set id to a dict with optional keys ``"ref"`` (value is the set's name),
  17. ``"supersets"`` (value is a collection of set ids), and ``"disjoints"``
  18. (value is a collection of set ids).
  19. Here is an example of set definitions, with natural numbers (N), integer
  20. numbers (Z), rational numbers (Q), real numbers (R), imaginary numbers
  21. (I) and complex numbers (C)::
  22. {
  23. 1: {"ref": "N", "supersets": [2]},
  24. 2: {"ref": "Z", "supersets": [3]},
  25. 3: {"ref": "Q", "supersets": [4]},
  26. 4: {"ref": "R", "supersets": [6]},
  27. 5: {"ref": "I", "supersets": [6], "disjoints": [4]},
  28. 6: {"ref": "C"},
  29. }
  30. """
  31. self.__leaves: dict[int | str, Leaf] = {}
  32. for leaf_id, info in definitions.items():
  33. ref = info['ref']
  34. assert ref != '*', "The set reference '*' is reserved for the universal set."
  35. leaf = Leaf(leaf_id, ref)
  36. self.__leaves[leaf_id] = leaf
  37. self.__leaves[ref] = leaf
  38. # compute transitive closure of subsets and supersets
  39. subsets = {leaf.id: leaf.subsets for leaf in self.__leaves.values()}
  40. supersets = {leaf.id: leaf.supersets for leaf in self.__leaves.values()}
  41. for leaf_id, info in definitions.items():
  42. for greater_id in info.get('supersets', ()):
  43. # transitive closure: smaller_ids <= leaf_id <= greater_id <= greater_ids
  44. smaller_ids = subsets[leaf_id]
  45. greater_ids = supersets[greater_id]
  46. for smaller_id in smaller_ids:
  47. supersets[smaller_id].update(greater_ids)
  48. for greater_id in greater_ids:
  49. subsets[greater_id].update(smaller_ids)
  50. # compute transitive closure of disjoint relation
  51. disjoints = {leaf.id: leaf.disjoints for leaf in self.__leaves.values()}
  52. for leaf_id, info in definitions.items():
  53. for distinct_id in info.get('disjoints', set()):
  54. # all subsets[leaf_id] are disjoint from all subsets[distinct_id]
  55. left_ids = subsets[leaf_id]
  56. right_ids = subsets[distinct_id]
  57. for left_id in left_ids:
  58. disjoints[left_id].update(right_ids)
  59. for right_id in right_ids:
  60. disjoints[right_id].update(left_ids)
  61. @property
  62. def empty(self) -> SetExpression:
  63. return EMPTY_UNION
  64. @property
  65. def universe(self) -> SetExpression:
  66. return UNIVERSAL_UNION
  67. def parse(self, refs: str, raise_if_not_found: bool = True) -> SetExpression:
  68. """ Return the set expression corresponding to ``refs``
  69. :param str refs: comma-separated list of set references
  70. optionally preceded by ``!`` (negative item). The result is
  71. an union between positive item who intersect every negative
  72. group.
  73. (e.g. ``base.group_user,base.group_portal,!base.group_system``)
  74. """
  75. positives: list[Leaf] = []
  76. negatives: list[Leaf] = []
  77. for xmlid in refs.split(','):
  78. if xmlid.startswith('!'):
  79. negatives.append(~self.__get_leaf(xmlid[1:], raise_if_not_found))
  80. else:
  81. positives.append(self.__get_leaf(xmlid, raise_if_not_found))
  82. if positives:
  83. return Union(Inter([leaf] + negatives) for leaf in positives)
  84. else:
  85. return Union([Inter(negatives)])
  86. def from_ids(self, ids: Iterable[int], keep_subsets: bool = False) -> SetExpression:
  87. """ Return the set expression corresponding to given set ids. """
  88. if keep_subsets:
  89. ids = set(ids)
  90. ids = [leaf_id for leaf_id in ids if not any((self.__leaves[leaf_id].subsets - {leaf_id}) & ids)]
  91. return Union(Inter([self.__leaves[leaf_id]]) for leaf_id in ids)
  92. def from_key(self, key: str) -> SetExpression:
  93. """ Return the set expression corresponding to the given key. """
  94. # union_tuple = tuple(tuple(tuple(leaf_id, negative), ...), ...)
  95. union_tuple = ast.literal_eval(key)
  96. return Union([
  97. Inter([
  98. ~leaf if negative else leaf
  99. for leaf_id, negative in inter_tuple
  100. for leaf in [self.__get_leaf(leaf_id, raise_if_not_found=False)]
  101. ], optimal=True)
  102. for inter_tuple in union_tuple
  103. ], optimal=True)
  104. def get_id(self, ref: LeafIdType) -> LeafIdType | None:
  105. """ Return a set id from its reference, or ``None`` if it does not exist. """
  106. if ref == '*':
  107. return UNIVERSAL_LEAF.id
  108. leaf = self.__leaves.get(ref)
  109. return None if leaf is None else leaf.id
  110. def __get_leaf(self, ref: str | int, raise_if_not_found: bool = True) -> Leaf:
  111. """ Return the group object from the string.
  112. :param str ref: the ref of a leaf
  113. """
  114. if ref == '*':
  115. return UNIVERSAL_LEAF
  116. if not raise_if_not_found and ref not in self.__leaves:
  117. return Leaf(UnknownId(ref), ref)
  118. return self.__leaves[ref]
  119. class SetExpression(ABC):
  120. """ An object that represents a combination of named sets with union,
  121. intersection and complement.
  122. """
  123. @abstractmethod
  124. def is_empty(self) -> bool:
  125. """ Returns whether ``self`` is the empty set, that contains nothing. """
  126. raise NotImplementedError()
  127. @abstractmethod
  128. def is_universal(self) -> bool:
  129. """ Returns whether ``self`` is the universal set, that contains all possible elements. """
  130. raise NotImplementedError()
  131. @abstractmethod
  132. def invert_intersect(self, factor: SetExpression) -> SetExpression | None:
  133. """ Performs the inverse operation of intersection (a sort of factorization)
  134. such that: ``self == result & factor``.
  135. """
  136. raise NotImplementedError()
  137. @abstractmethod
  138. def matches(self, user_group_ids: Iterable[int]) -> bool:
  139. """ Return whether the given group ids are included to ``self``. """
  140. raise NotImplementedError()
  141. @property
  142. @abstractmethod
  143. def key(self) -> str:
  144. """ Return a unique identifier for the expression. """
  145. raise NotImplementedError()
  146. @abstractmethod
  147. def __and__(self, other: SetExpression) -> SetExpression:
  148. raise NotImplementedError()
  149. @abstractmethod
  150. def __or__(self, other: SetExpression) -> SetExpression:
  151. raise NotImplementedError()
  152. @abstractmethod
  153. def __invert__(self) -> SetExpression:
  154. raise NotImplementedError()
  155. @abstractmethod
  156. def __eq__(self, other) -> bool:
  157. raise NotImplementedError()
  158. @abstractmethod
  159. def __le__(self, other: SetExpression) -> bool:
  160. raise NotImplementedError()
  161. @abstractmethod
  162. def __lt__(self, other: SetExpression) -> bool:
  163. raise NotImplementedError()
  164. @abstractmethod
  165. def __hash__(self):
  166. raise NotImplementedError()
  167. class Union(SetExpression):
  168. """ Implementation of a set expression, that represents it as a union of
  169. intersections of named sets or their complement.
  170. """
  171. def __init__(self, inters: Iterable[Inter] = (), optimal=False):
  172. if inters and not optimal:
  173. inters = self.__combine((), inters)
  174. self.__inters = sorted(inters, key=lambda inter: inter.key)
  175. self.__key = str(tuple(inter.key for inter in self.__inters))
  176. self.__hash = hash(self.__key)
  177. @property
  178. def key(self) -> str:
  179. return self.__key
  180. @staticmethod
  181. def __combine(inters: Iterable[Inter], inters_to_add: Iterable[Inter]) -> list[Inter]:
  182. """ Combine some existing union of intersections with extra intersections. """
  183. result = list(inters)
  184. todo = list(inters_to_add)
  185. while todo:
  186. inter_to_add = todo.pop()
  187. if inter_to_add.is_universal():
  188. return [UNIVERSAL_INTER]
  189. if inter_to_add.is_empty():
  190. continue
  191. for index, inter in enumerate(result):
  192. merged = inter._union_merge(inter_to_add)
  193. if merged is not None:
  194. result.pop(index)
  195. todo.append(merged)
  196. break
  197. else:
  198. result.append(inter_to_add)
  199. return result
  200. def is_empty(self) -> bool:
  201. """ Returns whether ``self`` is the empty set, that contains nothing. """
  202. return not self.__inters
  203. def is_universal(self) -> bool:
  204. """ Returns whether ``self`` is the universal set, that contains all possible elements. """
  205. return any(item.is_universal() for item in self.__inters)
  206. def invert_intersect(self, factor: SetExpression) -> Union | None:
  207. """ Performs the inverse operation of intersection (a sort of factorization)
  208. such that: ``self == result & factor``.
  209. """
  210. if factor == self:
  211. return UNIVERSAL_UNION
  212. rfactor = ~factor
  213. if rfactor.is_empty() or rfactor.is_universal():
  214. return None
  215. rself = ~self
  216. assert isinstance(rfactor, Union)
  217. inters = [inter for inter in rself.__inters if inter not in rfactor.__inters]
  218. if len(rself.__inters) - len(inters) != len(rfactor.__inters):
  219. # not possible to invert the intersection
  220. return None
  221. rself_value = Union(inters)
  222. return ~rself_value
  223. def __and__(self, other: SetExpression) -> Union:
  224. assert isinstance(other, Union)
  225. if self.is_universal():
  226. return other
  227. if other.is_universal():
  228. return self
  229. if self.is_empty() or other.is_empty():
  230. return EMPTY_UNION
  231. if self == other:
  232. return self
  233. return Union(
  234. self_inter & other_inter
  235. for self_inter in self.__inters
  236. for other_inter in other.__inters
  237. )
  238. def __or__(self, other: SetExpression) -> Union:
  239. assert isinstance(other, Union)
  240. if self.is_empty():
  241. return other
  242. if other.is_empty():
  243. return self
  244. if self.is_universal() or other.is_universal():
  245. return UNIVERSAL_UNION
  246. if self == other:
  247. return self
  248. inters = self.__combine(self.__inters, other.__inters)
  249. return Union(inters, optimal=True)
  250. def __invert__(self) -> Union:
  251. if self.is_empty():
  252. return UNIVERSAL_UNION
  253. if self.is_universal():
  254. return EMPTY_UNION
  255. # apply De Morgan's laws
  256. inverses_of_inters = [
  257. # ~(A & B) = ~A | ~B
  258. Union(Inter([~leaf]) for leaf in inter.leaves)
  259. for inter in self.__inters
  260. ]
  261. result = inverses_of_inters[0]
  262. # ~(A | B) = ~A & ~B
  263. for inverse in inverses_of_inters[1:]:
  264. result = result & inverse
  265. return result
  266. def matches(self, user_group_ids) -> bool:
  267. if self.is_empty() or not user_group_ids:
  268. return False
  269. if self.is_universal():
  270. return True
  271. user_group_ids = set(user_group_ids)
  272. return any(inter.matches(user_group_ids) for inter in self.__inters)
  273. def __bool__(self):
  274. raise NotImplementedError()
  275. def __eq__(self, other) -> bool:
  276. return isinstance(other, Union) and self.__key == other.__key
  277. def __le__(self, other: SetExpression) -> bool:
  278. if not isinstance(other, Union):
  279. return False
  280. if self.__key == other.__key:
  281. return True
  282. if self.is_universal() or other.is_empty():
  283. return False
  284. if other.is_universal() or self.is_empty():
  285. return True
  286. return all(
  287. any(self_inter <= other_inter for other_inter in other.__inters)
  288. for self_inter in self.__inters
  289. )
  290. def __lt__(self, other: SetExpression) -> bool:
  291. return self != other and self.__le__(other)
  292. def __str__(self):
  293. """ Returns an intersection union representation of groups using user-readable references.
  294. e.g. ('base.group_user' & 'base.group_multi_company') | ('base.group_portal' & ~'base.group_multi_company') | 'base.group_public'
  295. """
  296. if self.is_empty():
  297. return "~*"
  298. def leaf_to_str(leaf):
  299. return f"{'~' if leaf.negative else ''}{leaf.ref!r}"
  300. def inter_to_str(inter, wrapped=False):
  301. result = " & ".join(leaf_to_str(leaf) for leaf in inter.leaves) or "*"
  302. return f"({result})" if wrapped and len(inter.leaves) > 1 else result
  303. wrapped = len(self.__inters) > 1
  304. return " | ".join(inter_to_str(inter, wrapped) for inter in self.__inters)
  305. def __repr__(self):
  306. return repr(self.__str__())
  307. def __hash__(self):
  308. return self.__hash
  309. class Inter:
  310. """ Part of the implementation of a set expression, that represents an
  311. intersection of named sets or their complement.
  312. """
  313. __slots__ = ('key', 'leaves')
  314. def __init__(self, leaves: Iterable[Leaf] = (), optimal=False):
  315. if leaves and not optimal:
  316. leaves = self.__combine((), leaves)
  317. self.leaves: list[Leaf] = sorted(leaves, key=lambda leaf: leaf.key)
  318. self.key: tuple[tuple[LeafIdType, bool], ...] = tuple(leaf.key for leaf in self.leaves)
  319. @staticmethod
  320. def __combine(leaves: Iterable[Leaf], leaves_to_add: Iterable[Leaf]) -> list[Leaf]:
  321. """ Combine some existing intersection of leaves with extra leaves. """
  322. result = list(leaves)
  323. for leaf_to_add in leaves_to_add:
  324. for index, leaf in enumerate(result):
  325. if leaf.isdisjoint(leaf_to_add): # leaf & leaf_to_add = empty
  326. return [EMPTY_LEAF]
  327. if leaf <= leaf_to_add: # leaf & leaf_to_add = leaf
  328. break
  329. if leaf_to_add <= leaf: # leaf & leaf_to_add = leaf_to_add
  330. result[index] = leaf_to_add
  331. break
  332. else:
  333. if not leaf_to_add.is_universal():
  334. result.append(leaf_to_add)
  335. return result
  336. def is_empty(self) -> bool:
  337. return any(item.is_empty() for item in self.leaves)
  338. def is_universal(self) -> bool:
  339. """ Returns whether ``self`` is the universal set, that contains all possible elements. """
  340. return not self.leaves
  341. def matches(self, user_group_ids) -> bool:
  342. return all(leaf.matches(user_group_ids) for leaf in self.leaves)
  343. def _union_merge(self, other: Inter) -> Inter | None:
  344. """ Return the union of ``self`` with another intersection, if it can be
  345. represented as an intersection. Otherwise return ``None``.
  346. """
  347. # the following covers cases like (A & B) | A -> A
  348. if self.is_universal() or other <= self:
  349. return self
  350. if self <= other:
  351. return other
  352. # combine complementary parts: (A & ~B) | (A & B) -> A
  353. if len(self.leaves) == len(other.leaves):
  354. opposite_index = None
  355. # we use the property that __leaves are ordered
  356. for index, self_leaf, other_leaf in zip(range(len(self.leaves)), self.leaves, other.leaves):
  357. if self_leaf.id != other_leaf.id:
  358. return None
  359. if self_leaf.negative != other_leaf.negative:
  360. if opposite_index is not None:
  361. return None # we already have two opposite leaves
  362. opposite_index = index
  363. if opposite_index is not None:
  364. leaves = list(self.leaves)
  365. leaves.pop(opposite_index)
  366. return Inter(leaves, optimal=True)
  367. return None
  368. def __and__(self, other: Inter) -> Inter:
  369. if self.is_empty() or other.is_empty():
  370. return EMPTY_INTER
  371. if self.is_universal():
  372. return other
  373. if other.is_universal():
  374. return self
  375. leaves = self.__combine(self.leaves, other.leaves)
  376. return Inter(leaves, optimal=True)
  377. def __eq__(self, other) -> bool:
  378. return isinstance(other, Inter) and self.key == other.key
  379. def __le__(self, other: Inter) -> bool:
  380. return self.key == other.key or all(
  381. any(self_leaf <= other_leaf for self_leaf in self.leaves)
  382. for other_leaf in other.leaves
  383. )
  384. def __lt__(self, other: Inter) -> bool:
  385. return self != other and self <= other
  386. def __hash__(self):
  387. return hash(self.key)
  388. class Leaf:
  389. """ Part of the implementation of a set expression, that represents a named
  390. set or its complement.
  391. """
  392. __slots__ = ('disjoints', 'id', 'inverse', 'key', 'negative', 'ref', 'subsets', 'supersets')
  393. def __init__(self, leaf_id: LeafIdType, ref: str | int | None = None, negative: bool = False):
  394. self.id = leaf_id
  395. self.ref = ref or str(leaf_id)
  396. self.negative = bool(negative)
  397. self.key: tuple[LeafIdType, bool] = (leaf_id, self.negative)
  398. self.subsets: set[LeafIdType] = {leaf_id} # all the leaf ids that are <= self
  399. self.supersets: set[LeafIdType] = {leaf_id} # all the leaf ids that are >= self
  400. self.disjoints: set[LeafIdType] = set() # all the leaf ids disjoint from self
  401. self.inverse: Leaf | None = None
  402. def __invert__(self) -> Leaf:
  403. if self.inverse is None:
  404. self.inverse = Leaf(self.id, self.ref, negative=not self.negative)
  405. self.inverse.inverse = self
  406. self.inverse.subsets = self.subsets
  407. self.inverse.supersets = self.supersets
  408. self.inverse.disjoints = self.disjoints
  409. return self.inverse
  410. def is_empty(self) -> bool:
  411. return self.ref == '*' and self.negative
  412. def is_universal(self) -> bool:
  413. return self.ref == '*' and not self.negative
  414. def isdisjoint(self, other: Leaf) -> bool:
  415. if self.negative:
  416. return other <= ~self
  417. elif other.negative:
  418. return self <= ~other
  419. else:
  420. return self.id in other.disjoints
  421. def matches(self, user_group_ids: Collection[int]) -> bool:
  422. return (self.id not in user_group_ids) if self.negative else (self.id in user_group_ids)
  423. def __eq__(self, other) -> bool:
  424. return isinstance(other, Leaf) and self.key == other.key
  425. def __le__(self, other: Leaf) -> bool:
  426. if self.is_empty() or other.is_universal():
  427. return True
  428. elif self.is_universal() or other.is_empty():
  429. return False
  430. elif self.negative:
  431. return other.negative and ~other <= ~self
  432. elif other.negative:
  433. return self.id in other.disjoints
  434. else:
  435. return self.id in other.subsets
  436. def __lt__(self, other: Leaf) -> bool:
  437. return self != other and self <= other
  438. def __hash__(self):
  439. return hash(self.key)
  440. class UnknownId(str):
  441. """ Special id object for unknown leaves. It behaves as being strictly
  442. greater than any other kind of id.
  443. """
  444. __slots__ = ()
  445. def __lt__(self, other) -> bool:
  446. if isinstance(other, UnknownId):
  447. return super().__lt__(other)
  448. return False
  449. def __gt__(self, other) -> bool:
  450. if isinstance(other, UnknownId):
  451. return super().__gt__(other)
  452. return True
  453. LeafIdType = int | typing.Literal["*"] | UnknownId
  454. # constants
  455. UNIVERSAL_LEAF = Leaf('*')
  456. EMPTY_LEAF = ~UNIVERSAL_LEAF
  457. EMPTY_INTER = Inter([EMPTY_LEAF])
  458. UNIVERSAL_INTER = Inter()
  459. EMPTY_UNION = Union()
  460. UNIVERSAL_UNION = Union([UNIVERSAL_INTER])
上海开阖软件有限公司 沪ICP备12045867号-1