Coverage for src/_griffe/c3linear.py: 92.06%
53 statements
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 01:34 +0200
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 01:34 +0200
1# This module contains a single function, `c3linear_merge`.
2# The function is generic enough to be in its own module.
3#
4# - Copyright (c) 2019 Vitaly R. Samigullin
5# - Adapted from https://github.com/pilosus/c3linear
6# - Adapted from https://github.com/tristanlatr/pydocspec
8from __future__ import annotations
10from collections import deque
11from itertools import islice
12from typing import TypeVar
14_T = TypeVar("_T")
17class _Dependency(deque[_T]):
18 """A class representing a (doubly-ended) queue of items."""
20 @property
21 def head(self) -> _T | None:
22 """Head of the dependency."""
23 try:
24 return self[0]
25 except IndexError:
26 return None
28 @property
29 def tail(self) -> islice:
30 """Tail of the dependency.
32 The `islice` object is sufficient for iteration or testing membership (`in`).
33 """
34 try:
35 return islice(self, 1, self.__len__())
36 except (ValueError, IndexError):
37 return islice([], 0, 0)
40class _DependencyList:
41 """A class representing a list of linearizations (dependencies).
43 The last element of DependencyList is a list of parents.
44 It's needed to the merge process preserves the local
45 precedence order of direct parent classes.
46 """
48 def __init__(self, *lists: list[_T | None]) -> None:
49 """Initialize the list.
51 Parameters:
52 *lists: Lists of items.
53 """
54 self._lists = [_Dependency(lst) for lst in lists]
56 def __contains__(self, item: _T) -> bool:
57 """Return True if any linearization's tail contains an item."""
58 return any(item in lst.tail for lst in self._lists)
60 def __len__(self) -> int:
61 size = len(self._lists)
62 return (size - 1) if size else 0
64 def __repr__(self) -> str:
65 return self._lists.__repr__()
67 @property
68 def heads(self) -> list[_T | None]:
69 """Return the heads."""
70 return [lst.head for lst in self._lists] # type: ignore[misc]
72 @property
73 def tails(self) -> _DependencyList:
74 """Return self so that `__contains__` could be called."""
75 return self
77 @property
78 def exhausted(self) -> bool:
79 """True if all elements of the lists are exhausted."""
80 return all(len(x) == 0 for x in self._lists)
82 def remove(self, item: _T | None) -> None:
83 """Remove an item from the lists.
85 Once an item removed from heads, the leftmost elements of the tails
86 get promoted to become the new heads.
87 """
88 for i in self._lists:
89 if i and i.head == item:
90 i.popleft()
93def c3linear_merge(*lists: list[_T]) -> list[_T]:
94 """Merge lists of lists in the order defined by the C3Linear algorithm.
96 Parameters:
97 *lists: Lists of items.
99 Returns:
100 The merged list of items.
101 """
102 result: list[_T] = []
103 linearizations = _DependencyList(*lists) # type: ignore[arg-type]
105 while True:
106 if linearizations.exhausted:
107 return result
109 for head in linearizations.heads:
110 if head and (head not in linearizations.tails):
111 result.append(head) # type: ignore[arg-type]
112 linearizations.remove(head)
114 # Once candidate is found, continue iteration
115 # from the first element of the list.
116 break
117 else:
118 # Loop never broke, no linearization could possibly be found.
119 raise ValueError("Cannot compute C3 linearization")