Coverage for src/_griffe/c3linear.py: 93.75%
52 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 16:47 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 16:47 +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 itertools import islice
11from typing import Deque, TypeVar
13_T = TypeVar("_T")
16class _Dependency(Deque[_T]):
17 """A class representing a (doubly-ended) queue of items."""
19 @property
20 def head(self) -> _T | None:
21 """Head of the dependency."""
22 try:
23 return self[0]
24 except IndexError:
25 return None
27 @property
28 def tail(self) -> islice:
29 """Tail of the dependency.
31 The `islice` object is sufficient for iteration or testing membership (`in`).
32 """
33 try:
34 return islice(self, 1, self.__len__())
35 except (ValueError, IndexError):
36 return islice([], 0, 0)
39class _DependencyList:
40 """A class representing a list of linearizations (dependencies).
42 The last element of DependencyList is a list of parents.
43 It's needed to the merge process preserves the local
44 precedence order of direct parent classes.
45 """
47 def __init__(self, *lists: list[_T | None]) -> None:
48 """Initialize the list.
50 Parameters:
51 *lists: Lists of items.
52 """
53 self._lists = [_Dependency(lst) for lst in lists]
55 def __contains__(self, item: _T) -> bool:
56 """Return True if any linearization's tail contains an item."""
57 return any(item in lst.tail for lst in self._lists)
59 def __len__(self) -> int:
60 size = len(self._lists)
61 return (size - 1) if size else 0
63 def __repr__(self) -> str:
64 return self._lists.__repr__()
66 @property
67 def heads(self) -> list[_T | None]:
68 """Return the heads."""
69 return [lst.head for lst in self._lists] # type: ignore[misc]
71 @property
72 def tails(self) -> _DependencyList:
73 """Return self so that `__contains__` could be called."""
74 return self
76 @property
77 def exhausted(self) -> bool:
78 """True if all elements of the lists are exhausted."""
79 return all(len(x) == 0 for x in self._lists)
81 def remove(self, item: _T | None) -> None:
82 """Remove an item from the lists.
84 Once an item removed from heads, the leftmost elements of the tails
85 get promoted to become the new heads.
86 """
87 for i in self._lists:
88 if i and i.head == item:
89 i.popleft()
92def c3linear_merge(*lists: list[_T]) -> list[_T]:
93 """Merge lists of lists in the order defined by the C3Linear algorithm.
95 Parameters:
96 *lists: Lists of items.
98 Returns:
99 The merged list of items.
100 """
101 result: list[_T] = []
102 linearizations = _DependencyList(*lists) # type: ignore[arg-type]
104 while True:
105 if linearizations.exhausted:
106 return result
108 for head in linearizations.heads:
109 if head and (head not in linearizations.tails):
110 result.append(head) # type: ignore[arg-type]
111 linearizations.remove(head)
113 # Once candidate is found, continue iteration
114 # from the first element of the list.
115 break
116 else:
117 # Loop never broke, no linearization could possibly be found.
118 raise ValueError("Cannot compute C3 linearization")