diff --git a/CHANGELOG.md b/CHANGELOG.md index 07ff7c9d56220b11e1a93cf3c1e4d1c59d9ab74e..3af8fc876de102834af36e4dcd1428c9940b34d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ **bugfix release** * fixed import problem +* added diff --git a/docs/coding/structures.rst b/docs/coding/structures.rst index e7486d0e98c87b4ba1607b2d609eefbb90bf0580..79b679ff16e34446728494b52dd4d56575223efc 100644 --- a/docs/coding/structures.rst +++ b/docs/coding/structures.rst @@ -6,6 +6,12 @@ Structures :members: +PushIterable +------------ + +.. autoclass:: satella.coding.structures.PushIterable + :members: + SyncableDroppable ----------------- diff --git a/satella/__init__.py b/satella/__init__.py index 4895c882f533b90563a406c778c38e7358f59620..e824526d10fda2a26f44ff378e117a98e44db940 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.15.3' +__version__ = '2.15.4' diff --git a/satella/coding/structures/__init__.py b/satella/coding/structures/__init__.py index c1d21f02a29520cef933d7cb08d42721dc4209d0..e0efee2bedfa722402e4b9061e84ebb95675d5cb 100644 --- a/satella/coding/structures/__init__.py +++ b/satella/coding/structures/__init__.py @@ -18,8 +18,10 @@ from .typednamedtuple import typednamedtuple from .lru import LRU from .syncable_droppable import DBStorage, SyncableDroppable from .tuples import Vector +from .push_iterable import PushIterable __all__ = [ + 'PushIterable', 'Vector', 'DBStorage', 'SyncableDroppable', 'LRU', diff --git a/satella/coding/structures/push_iterable.py b/satella/coding/structures/push_iterable.py new file mode 100644 index 0000000000000000000000000000000000000000..4daad6ef7f2f47df979b6b5975240ba3f369aa21 --- /dev/null +++ b/satella/coding/structures/push_iterable.py @@ -0,0 +1,66 @@ +import typing as tp +from collections import deque + +from satella.coding.typing import T + + +class PushIterable(tp.Generic[T]): + """ + An iterable that you can add elements to it's head, and they will be popped + before this iterable is consumed when a pop is called. + + Example: + + .. code-block:: python + + a = PushIterable([1, 2, 3]) + assert a.pop() == 1 + a.push(0) + assert a.pop() == 0 + assert a.pop() == 2 + a.push(0) + a.push(1) + assert a.pop() == 1 + assert a.pop() == 0 + a.push_left(0) + a.push_left(1) + assert a.pop() == 0 + assert a.pop() == 1 + assert a.pop() == 3 + assertRaises(StopIteration, a.pop) + """ + def __init__(self, iterable: tp.Iterable[T]): + self.iterable = iter(iterable) + self.collection = deque() + + def pop(self) -> T: + """ + Return next element from the stack + :return: a next element + """ + if not self.collection: + return next(self.iterable) + else: + return self.collection.pop() + + def __iter__(self): + return self + + def __next__(self) -> T: + return self.pop() + + def push(self, item: T) -> None: + """ + Push an item so that next pop will retrieve this item + + :param item: item to push + """ + self.collection.append(item) + + def push_left(self, item): + """ + Push an item so that last pop from internal buffer will retrieve this item + + :param item: item to push + """ + self.collection.appendleft(item) diff --git a/tests/test_coding/test_structures.py b/tests/test_coding/test_structures.py index 8b5448490e4820bbc9ee432db0107d4596f09b86..9d7a7605b8b5fcb1f1aa1a6f40d068b8ee031b4a 100644 --- a/tests/test_coding/test_structures.py +++ b/tests/test_coding/test_structures.py @@ -14,11 +14,29 @@ from satella.coding.structures import TimeBasedHeap, Heap, typednamedtuple, \ DirtyDict, KeyAwareDefaultDict, Proxy, ReprableMixin, TimeBasedSetHeap, ExpiringEntryDict, SelfCleaningDefaultDict, \ CacheDict, StrEqHashableMixin, ComparableIntEnum, HashableIntEnum, ComparableAndHashableBy, \ ComparableAndHashableByInt, SparseMatrix, ExclusiveWritebackCache, Subqueue, \ - CountingDict, ComparableEnum, LRU, LRUCacheDict, Vector, DefaultDict + CountingDict, ComparableEnum, LRU, LRUCacheDict, Vector, DefaultDict, PushIterable class TestMisc(unittest.TestCase): + def test_push_iterable(self): + + a = PushIterable([1, 2, 3]) + self.assertEqual(a.pop(), 1) + a.push(0) + self.assertEqual(a.pop(), 0) + self.assertEqual(a.pop(), 2) + a.push(0) + a.push(1) + self.assertEqual(a.pop(), 1) + self.assertEqual(a.pop(), 0) + a.push_left(0) + a.push_left(1) + self.assertEqual(a.pop(), 0) + self.assertEqual(a.pop(), 1) + self.assertEqual(a.pop(), 3) + self.assertRaises(StopIteration, a.pop) + def test_default_dict(self): a = DefaultDict(lambda: '') a[2]