Skip to content

Commit

Permalink
Add rlocate
Browse files Browse the repository at this point in the history
  • Loading branch information
bbayles committed May 24, 2018
1 parent 549680c commit b2f0cd4
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/api.rst
Expand Up @@ -127,6 +127,7 @@ These tools return summarized or aggregated data from an iterable.
.. autofunction:: one
.. autofunction:: unique_to_each
.. autofunction:: locate(iterable, pred=bool)
.. autofunction:: rlocate(iterable, pred=bool)
.. autofunction:: consecutive_groups(iterable, ordering=lambda x: x)
.. autofunction:: exactly_n(iterable, n, predicate=bool)
.. autoclass:: run_length
Expand Down
33 changes: 33 additions & 0 deletions more_itertools/more.py
Expand Up @@ -60,6 +60,7 @@
'one',
'padded',
'peekable',
'rlocate',
'rstrip',
'run_length',
'seekable',
Expand Down Expand Up @@ -2066,3 +2067,35 @@ def map_reduce(iterable, keyfunc, valuefunc=None, reducefunc=None):

ret.default_factory = None
return ret


def rlocate(iterable, pred=bool):
"""Yield the index of each item in *iterable* for which *pred* returns
``True``, starting from the right and moving left.
*pred* defaults to :func:`bool`, which will select truthy items:
>>> list(rlocate([0, 1, 1, 0, 1, 0, 0])) # Truthy at 1, 2, and 4
[4, 2, 1]
Set *pred* to a custom function to, e.g., find the indexes for a particular
item:
>>> iterable = iter('abcb')
>>> pred = lambda x: x == 'b'
>>> list(rlocate(iterable, pred))
[3, 1]
Beware, this function won't return anything for infinite iterables.
If *iterable* is reversible, ``rlocate`` will reverse it and search from
the right. Otherwise, it will search from the left and return the results
in reverse order.
See :func:`locate` to for other example applications.
"""
try:
len_iter = len(iterable)
return (len_iter - i - 1 for i in locate(reversed(iterable), pred))
except TypeError:
return reversed(list(locate(iterable, pred)))
31 changes: 31 additions & 0 deletions more_itertools/tests/test_more.py
Expand Up @@ -1846,3 +1846,34 @@ def test_ret(self):
d = mi.map_reduce([1, 0, 2, 0, 1, 0], bool)
self.assertEqual(d, {False: [0, 0, 0], True: [1, 2, 1]})
self.assertRaises(KeyError, lambda: d[None].append(1))


class RlocateTests(TestCase):
def test_default_pred(self):
iterable = [0, 1, 1, 0, 1, 0, 0]
for it in (iterable[:], iter(iterable)):
actual = list(mi.rlocate(it))
expected = [4, 2, 1]
self.assertEqual(actual, expected)

def test_no_matches(self):
iterable = [0, 0, 0]
for it in (iterable[:], iter(iterable)):
actual = list(mi.rlocate(it))
expected = []
self.assertEqual(actual, expected)

def test_custom_pred(self):
iterable = ['0', 1, 1, '0', 1, '0', '0']
pred = lambda x: x == '0'
for it in (iterable[:], iter(iterable)):
actual = list(mi.rlocate(it, pred))
expected = [6, 5, 3, 0]
self.assertEqual(actual, expected)

def test_efficient_reversal(self):
iterable = range(10 ** 10) # Is efficiently reversible
target = 10 ** 10 - 2
pred = lambda x: x == target # Find-able from the right
actual = next(mi.rlocate(iterable, pred))
self.assertEqual(actual, target)

0 comments on commit b2f0cd4

Please sign in to comment.