diff --git a/concepts/contexts.py b/concepts/contexts.py index 0479e99..152ac19 100644 --- a/concepts/contexts.py +++ b/concepts/contexts.py @@ -9,6 +9,7 @@ from . import formats from . import junctors from . import lattices +from . import lazy_lattices from . import matrices from . import tools @@ -513,6 +514,15 @@ def lattice(self) -> 'lattices.Lattice': """ return lattices.Lattice(self) + @tools.lazyproperty + def lazy_lattice(self) -> 'lazy_lattices.LazyLattice': + """The lazy concept lattice of the formal context. + + Returns: + lazy_lattices.LazyLattice: Cached or new :class:`lazy_lattices.LazyLattice` instance. + """ + return lazy_lattices.LazyLattice(self) + class ExportableMixin: diff --git a/concepts/lazy_lattice_members.py b/concepts/lazy_lattice_members.py new file mode 100644 index 0000000..cc996e0 --- /dev/null +++ b/concepts/lazy_lattice_members.py @@ -0,0 +1,101 @@ +import typing + +from .algorithms import lindig +from .lattice_members import OrderableMixin + + +class LazyPair: + def __init__(self, + lattice, + extent, + intent, + upper=None, + lower=None) -> None: + self.lattice = lattice + self._extent = extent + self._intent = intent + self._upper_neighbors = upper + self._lower_neighbors = lower + + @property + def upper_neighbors(self): + if self._upper_neighbors is None: + neighbors = lindig.neighbors(self._extent, Objects=self.lattice._context._Objects) + + shortlex = self.lattice._shortlex + + upper = (self.lattice[extent.members()] for extent, _ in neighbors) + self._upper_neighbors = tuple(sorted(upper, key=shortlex)) + + return self._upper_neighbors + + @property + def lower_neighbors(self): + if self._lower_neighbors is None: + neighbors = lindig.neighbors(self._intent, Objects=self.lattice._context._Properties) + + longlex = self.lattice._longlex + + lower = (self.lattice[intent.members()] for intent, _ in neighbors) + self._lower_neighbors = tuple(sorted(lower, key=longlex)) + + return self._lower_neighbors + + @property + def extent(self) -> typing.Tuple[str, ...]: + return self._extent.members() + + @property + def intent(self) -> typing.Tuple[str, ...]: + return self._intent.members() + + def _eq(self, other): + if not isinstance(other, LazyConcept): + return NotImplemented + + if (other._extent.members() != self._extent.members() + or other._intent.members() != self._intent.members()): + return False + + return True + + +class LazyRelationsMixin: + def incompatible_with(self, other: 'LazyConcept') -> bool: + return not self._extent & other._extent + + +class LazyTransformableMixin: + def join(self, other: 'LazyConcept') -> 'LazyConcept': + common = self._extent | other._extent + extent = self.lattice._context._extents.double(common) + return self.lattice[extent.members()] + + __or__ = join + + def meet(self, other: 'LazyConcept') -> 'LazyConcept': + common = self._extent & other._extent + extent = self.lattice._context._extents.double(common) + return self.lattice[extent.members()] + + __and__ = meet + + +class LazyFormattingMixin: + def __str__(self) -> str: + extent = ', '.join(self._extent.members()) + intent = ' '.join(self._intent.members()) + return f'{{{extent}}} <-> [{intent}]' + + def __repr__(self) -> str: + return f'<{self.__class__.__name__} {self}>' + + +class LazyConcept(LazyRelationsMixin, OrderableMixin, LazyFormattingMixin, LazyPair): + def minimal(self) -> typing.Tuple[str, ...]: + return self.lattice._context._minimal(self._extent, + self._intent).members() + + def attributes(self) -> typing.Iterator[typing.Tuple[str]]: + minimize = self.lattice._context._minimize(self._extent, self._intent) + return (i.members() for i in minimize) \ No newline at end of file diff --git a/concepts/lazy_lattices.py b/concepts/lazy_lattices.py new file mode 100644 index 0000000..e5bc45f --- /dev/null +++ b/concepts/lazy_lattices.py @@ -0,0 +1,63 @@ +import typing + +from . import contexts +from .lazy_lattice_members import LazyConcept + + +class LazyFormattingMixin: + def __str__(self) -> str: + concepts = '\n'.join(f' {c}' for c in self._concepts) + return f'{self!r}\n{concepts}' + + def __repr__(self) -> str: + return (f'<{self.__class__.__name__} object' + f' {len(self)} concepts' + f' at {id(self):#x}>') + + +class LazyLattice(LazyFormattingMixin): + @staticmethod + def _longlex(concept): + return concept._extent.longlex() + + @staticmethod + def _shortlex(concept): + return concept._extent.shortlex() + + def __init__(self, context: 'contexts.Context'): + self._context = context + self._concepts = [] + self._mapping = {} + + def __repr__(self) -> str: + return (f'<{self.__class__.__name__} object' + f' {len(self)} concepts' + f' at {id(self):#x}>') + + def __getitem__(self, key: typing.Tuple[str, ...]) -> LazyConcept: + if not key: + key = self._context._Objects.supremum.members() + + extent, intent = self._context.__getitem__(key, raw=True) + + if extent not in self._mapping: + new_concept = LazyConcept(self, extent, intent) + self._mapping[extent] = new_concept + self._concepts.append(new_concept) + self._concepts.sort(key=self._shortlex) + + return self._mapping[extent] + + def __iter__(self) -> typing.Iterator[LazyConcept]: + return iter(self._concepts) + + def __len__(self) -> int: + return len(self._concepts) + + + + + + + +