Skip to content

Commit

Permalink
Add Multiple Iterable maps to the project (#1)
Browse files Browse the repository at this point in the history
* Add Multiple iterable maps to the project

* Fix code hygiene issues
  • Loading branch information
shashankrnr32 committed Jun 9, 2023
1 parent 4b460f7 commit 5869c15
Show file tree
Hide file tree
Showing 17 changed files with 606 additions and 215 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/code_quality_checks_3_10.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ jobs:
- name: Check format with black
run: poetry run black --check pycommons/ tests/
- name: Check type hinting with mypy
run: poetry run mypy --strict --config-file=mypy.ini pycommons/
run: poetry run mypy --namespace-packages -p pycommons.collections --strict --config-file=mypy.ini

2 changes: 1 addition & 1 deletion .github/workflows/code_quality_checks_3_8.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ jobs:
- name: Check format with black
run: poetry run black --check pycommons/ tests/
- name: Check type hinting with mypy
run: poetry run mypy --strict --config-file=mypy.ini pycommons/
run: poetry run mypy --namespace-packages -p pycommons.collections --strict --config-file=mypy.ini

2 changes: 1 addition & 1 deletion .github/workflows/code_quality_checks_3_9.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ jobs:
- name: Check format with black
run: poetry run black --check pycommons/ tests/
- name: Check type hinting with mypy
run: poetry run mypy --strict --config-file=mypy.ini pycommons/
run: poetry run mypy --namespace-packages -p pycommons.collections --strict --config-file=mypy.ini

428 changes: 219 additions & 209 deletions poetry.lock

Large diffs are not rendered by default.

Empty file removed pycommons/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions pycommons/collections/maps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .defaulted import DefaultedMap
from .iterable import IterableMap, MapIterator
from .lazy import LazyMap, LazyOrderedMap
from .multi_valued import MultiValuedMap
from .ordered import OrderedMap
from .sized import FixedSizeMap, SingletonMap
from .unmodifiable import UnmodifiableMap, UnmodifiableLateInitMap, UnmodifiableMapIterator
15 changes: 15 additions & 0 deletions pycommons/collections/maps/defaulted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import TypeVar, Generic, Any

from pycommons.collections.maps.iterable import IterableMap

_K = TypeVar("_K")
_V = TypeVar("_V")


class DefaultedMap(IterableMap[_K, _V], Generic[_K, _V]):
def __init__(self, default_value: _V, *args: Any, **kwargs: Any):
self._default_value = default_value
super().__init__(*args, **kwargs)

def __getitem__(self, item: _K) -> _V:
return self.data.get(item, self._default_value)
67 changes: 67 additions & 0 deletions pycommons/collections/maps/iterable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from __future__ import annotations

import typing
from collections import UserDict # pylint: disable=E0611
from typing import Iterator
from typing import TypeVar, Dict, Generic, Optional

_K = TypeVar("_K")
_V = TypeVar("_V")


class MapIterator(Iterator["MapIterator[_K, _V]"], Generic[_K, _V]):
def __init__(
self,
data: Dict[_K, _V],
items_iterator: Iterator[typing.Tuple[_K, _V]],
current_key: Optional[_K] = None,
current_value: Optional[_V] = None,
):
self._data = data
self._items_iterator: Iterator[typing.Tuple[_K, _V]] = items_iterator
self._current_key: Optional[_K] = current_key
self._current_value: Optional[_V] = current_value

def get_key(self) -> _K:
return self.key

def get_value(self) -> _V:
return self.value

def set_value(self, val: _V) -> _V:
_prev_val = self._current_value
self._current_value = val
self._data[typing.cast(_K, self._current_key)] = self._current_value
return typing.cast(_V, _prev_val)

@property
def key(self) -> _K:
return typing.cast(_K, self._current_key)

@property
def value(self) -> _V:
return typing.cast(_V, self._current_value)

@value.setter
def value(self, val: _V) -> None:
self.set_value(val)

def __next__(self) -> MapIterator[_K, _V]:
_next = next(self._items_iterator)
return MapIterator(self._data, self._items_iterator, _next[0], _next[1])


class IterableMap(UserDict, Generic[_K, _V]): # type: ignore
data: Dict[_K, _V]

def __iter__(self) -> MapIterator[_K, _V]:
return self.items_iterator()

def items_iterator(self) -> MapIterator[_K, _V]:
return MapIterator(self.data, iter(self.data.items()))

def keys_iterator(self) -> Iterator[_K]:
return iter(self.data.keys())

def values_iterator(self) -> Iterator[_V]:
return iter(self.values())
26 changes: 26 additions & 0 deletions pycommons/collections/maps/lazy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import TypeVar, Generic, Any

from pycommons.collections.maps.iterable import IterableMap
from pycommons.collections.maps.ordered import OrderedMap
from pycommons.lang.function import Function

_K = TypeVar("_K")
_V = TypeVar("_V")


class LazyMap(IterableMap[_K, _V], Generic[_K, _V]):
def __init__(self, factory: Function[_K, _V], *args: Any, **kwargs: Any):
self._factory = factory
super().__init__(*args, **kwargs)

def __getitem__(self, item: _K) -> _V:
if item not in self.data:
self.data[item] = self._factory.apply(item)

return self.data[item]


class LazyOrderedMap(LazyMap[_K, _V], OrderedMap[_K, _V], Generic[_K, _V]):
def __init__(self, factory: Function[_K, _V]):
LazyMap.__init__(self, factory)
OrderedMap.__init__(self)
21 changes: 21 additions & 0 deletions pycommons/collections/maps/multi_valued.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import typing
from typing import TypeVar, Any, Generic

from pycommons.collections.maps.iterable import IterableMap
from pycommons.collections.sets.ordered import OrderedSet

_K = TypeVar("_K")
_V = TypeVar("_V")


class MultiValuedMap(IterableMap[_K, OrderedSet[_V]], Generic[_K, _V]):
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)

def __setitem__(self, key: _K, value: _V) -> None:
if key in self and isinstance(self.data[key], OrderedSet):
self.data[key].add(value)
elif key in self and not isinstance(self.data[key], OrderedSet):
self.data[key] = OrderedSet((typing.cast(_V, self.data[key]), value))
elif key not in self:
self.data[key] = OrderedSet((value,))
20 changes: 20 additions & 0 deletions pycommons/collections/maps/ordered.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import collections
from typing import TypeVar

from pycommons.collections.maps.iterable import IterableMap

_K = TypeVar("_K")
_V = TypeVar("_V")


class OrderedMap(IterableMap[_K, _V]):
def __init__(self) -> None:
super().__init__()
self.data = collections.OrderedDict()

def __str__(self) -> str:
_str_list = []
for map_iterator in self:
_str_list.append(f"{repr(map_iterator.key)}: {repr(map_iterator.value)}")

return f"{{{', '.join(_str_list)}}}"
64 changes: 64 additions & 0 deletions pycommons/collections/maps/predicated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from typing import Generic, TypeVar, Any

from pycommons.collections.maps.iterable import IterableMap
from pycommons.collections.maps.ordered import OrderedMap
from pycommons.lang.function import Predicate, BiPredicate

_K = TypeVar("_K")
_V = TypeVar("_V")


class PredicatedMap(IterableMap[_K, _V], Generic[_K, _V]):
def __init__(
self,
key_predicate: Predicate[_K],
value_predicate: Predicate[_V],
*args: Any,
**kwargs: Any
):
self._key_predicate = key_predicate
self._value_predicate = value_predicate
super().__init__(*args, *kwargs)

def __setitem__(self, key: _K, value: _V) -> None:
self.validate_exceptionally(key, value)
super().__setitem__(key, value)

def validate(self, key: _K, value: _V) -> bool:
return self.validate_key(key) and self.validate_value(value)

def validate_exceptionally(self, key: _K, value: _V) -> None:
if not self.validate_key(key):
raise ValueError("Predicate not passing for the key passed")

if not self.validate_value(value):
raise ValueError("Predicate not passing for the value passed")

def validate_key(self, key: _K) -> bool:
return self._key_predicate.test(key)

def validate_value(self, value: _V) -> bool:
return self._value_predicate.test(value)


class PredicatedOrderedMap(PredicatedMap[_K, _V], OrderedMap[_K, _V], Generic[_K, _V]):
def __init__(self, key_predicate: Predicate[_K], value_predicate: Predicate[_V]):
PredicatedMap.__init__(self, key_predicate, value_predicate)
OrderedMap.__init__(self)


class CompositePredicatedMap(IterableMap[_K, _V], Generic[_K, _V]):
def __init__(self, predicate: BiPredicate[_K, _V], *args: Any, **kwargs: Any):
self._predicate = predicate
super().__init__(*args, **kwargs)

def __setitem__(self, key: _K, value: _V) -> None:
self.validate_exceptionally(key, value)
super().__setitem__(key, value)

def validate(self, key: _K, value: _V) -> bool:
return self._predicate.test(key, value)

def validate_exceptionally(self, key: _K, value: _V) -> None:
if not self.validate(key, value):
raise ValueError("Predicate not passing for the key passed")
41 changes: 41 additions & 0 deletions pycommons/collections/maps/sized.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import abc
from typing import TypeVar, Generic, Any

from pycommons.collections.maps.iterable import IterableMap

_K = TypeVar("_K")
_V = TypeVar("_V")


class BoundedMap(IterableMap[_K, _V], abc.ABC, Generic[_K, _V]):
@abc.abstractmethod
def is_full(self) -> bool:
...

@abc.abstractmethod
def max_size(self) -> int:
...


class FixedSizeMap(BoundedMap[_K, _V], Generic[_K, _V]):
def is_full(self) -> bool:
return len(self) == self._max_size - 1

def max_size(self) -> int:
return self._max_size

def __init__(self, size: int, *args: Any, **kwargs: Any):
self._max_size: int = size
super().__init__(*args, **kwargs)

def __setitem__(self, key: _K, value: _V) -> None:
if len(self.data) == self._max_size:
raise OverflowError(
f"Size breached for {self.__class__.__name__}(max_size={self.max_size()})"
)
super().__setitem__(key, value)


class SingletonMap(FixedSizeMap[_K, _V], Generic[_K, _V]):
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(1, *args, **kwargs)
83 changes: 83 additions & 0 deletions pycommons/collections/maps/unmodifiable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from __future__ import annotations

from typing import TypeVar, Tuple, Generic, Any, Union

from pycommons.collections.maps.iterable import MapIterator
from pycommons.collections.maps.sized import BoundedMap

_K = TypeVar("_K")
_V = TypeVar("_V")


class UnmodifiableMapIterator(MapIterator[_K, _V], Generic[_K, _V]):
def set_value(self, val: _V) -> _V:
raise TypeError(f"Cannot modify values in a {self.__class__.__name__}")

def __next__(self) -> UnmodifiableMapIterator[_K, _V]:
_next = next(self._items_iterator)
return UnmodifiableMapIterator(self._data, self._items_iterator, _next[0], _next[1])


class UnmodifiableMap(BoundedMap[_K, _V], Generic[_K, _V]):
__POP_DEFAULT_VALUE = object()

def is_full(self) -> bool:
return True

def max_size(self) -> int:
return self._max_size

def _allow_set_item(self, key: _K) -> bool:
return self.__init and key is not None

def __init__(self, *args: Any, **kwargs: Any) -> None:
self.__init: bool = True
super().__init__(*args, **kwargs)
self._max_size = len(self.data)
self.__init = False

def __setitem__(self, key: _K, value: _V) -> None:
if self._allow_set_item(key):
return super().__setitem__(key, value)
raise TypeError(f"Cannot modify {self.__class__.__name__}")

def popitem(self) -> Tuple[_K, _V]:
if self.__init:
return super().popitem()
raise TypeError(f"Cannot modify {self.__class__.__name__}")

def __delitem__(self, key: _K) -> None:
if self.__init:
return self.data.__delitem__(key)
raise TypeError(f"Cannot modify {self.__class__.__name__}")

def update(self, __m: Any, **kwargs: Any) -> None: # type: ignore
if self.__init:
return self.data.update(__m, **kwargs)
raise TypeError(f"Cannot modify {self.__class__.__name__}")

def pop(self, __key: Any, value: Union[_V, Any] = __POP_DEFAULT_VALUE) -> _V:
if self.__init:
if value == self.__POP_DEFAULT_VALUE:
return self.data.pop(__key)
return self.data.pop(__key, value)
raise TypeError(f"Cannot modify {self.__class__.__name__}")

def clear(self) -> None:
if self.__init:
return super().clear()
raise TypeError(f"Cannot modify {self.__class__.__name__}")

def items_iterator(self) -> MapIterator[_K, _V]:
return UnmodifiableMapIterator(self.data, iter(self.data.items()))


class UnmodifiableLateInitMap(UnmodifiableMap[_K, _V], Generic[_K, _V]):
def max_size(self) -> int:
return len(self.data)

def is_full(self) -> bool:
return False

def _allow_set_item(self, key: _K) -> bool:
return super()._allow_set_item(key) or key not in self.data
1 change: 1 addition & 0 deletions pycommons/collections/sets/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .ordered import OrderedSet
Loading

0 comments on commit 5869c15

Please sign in to comment.