diff --git a/README.md b/README.md index 6c60017..544dbfa 100644 --- a/README.md +++ b/README.md @@ -190,9 +190,9 @@ R.equals(float('nan'), float('nan')) # True - [x] 0.1.2 F - [x] 0.1.2 filter - [x] 0.1.2 find -- [ ] findIndex -- [ ] findLast -- [ ] findLastIndex +- [x] findIndex +- [x] findLast +- [x] findLastIndex - [x] 0.1.2 flatten - [x] 0.1.2 flip - [x] forEach diff --git a/ramda/__init__.py b/ramda/__init__.py index fc0f17d..7ca82f9 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -23,6 +23,9 @@ from .F import F from .filter import filter from .find import find +from .findIndex import findIndex +from .findLast import findLast +from .findLastIndex import findLastIndex from .flatten import flatten from .flip import flip from .forEach import forEach diff --git a/ramda/findIndex.py b/ramda/findIndex.py new file mode 100644 index 0000000..5f9db15 --- /dev/null +++ b/ramda/findIndex.py @@ -0,0 +1,16 @@ +from .private._curry2 import _curry2 +from .private._dispatchable import _dispatchable +from .private._xfindIndex import _xfindIndex + + +def inner_findIndex(fn, arr): + idx = 0 + length = len(arr) + while idx < length: + if fn(arr[idx]): + return idx + idx += 1 + return -1 + + +findIndex = _curry2(_dispatchable([], _xfindIndex, inner_findIndex)) diff --git a/ramda/findLast.py b/ramda/findLast.py new file mode 100644 index 0000000..62eb21e --- /dev/null +++ b/ramda/findLast.py @@ -0,0 +1,14 @@ +from .private._curry2 import _curry2 +from .private._dispatchable import _dispatchable +from .private._xfindLast import _xfindLast + + +def inner_findLast(fn, arr): + idx = len(arr) - 1 + while idx >= 0: + if fn(arr[idx]): + return arr[idx] + idx -= 1 + return None + +findLast = _curry2(_dispatchable([], _xfindLast, inner_findLast)) diff --git a/ramda/findLastIndex.py b/ramda/findLastIndex.py new file mode 100644 index 0000000..cfb3825 --- /dev/null +++ b/ramda/findLastIndex.py @@ -0,0 +1,15 @@ +from .private._curry2 import _curry2 +from .private._dispatchable import _dispatchable +from .private._xfindLastIndex import _xfindLastIndex + + +def inner_findLastIndex(fn, arr): + idx = len(arr) - 1 + while idx >= 0: + if fn(arr[idx]): + return idx + idx -= 1 + return -1 + + +findLastIndex = _curry2(_dispatchable([], _xfindLastIndex, inner_findLastIndex)) diff --git a/ramda/private/_xfindIndex.py b/ramda/private/_xfindIndex.py new file mode 100644 index 0000000..4c5badf --- /dev/null +++ b/ramda/private/_xfindIndex.py @@ -0,0 +1,26 @@ +from ._helper import getAttribute +from ._reduced import _reduced +from ._xfBase import XfBase + + +class XFindIndex(XfBase): + def __init__(self, f, xf): + self.xf = xf + self.f = f + self.idx = -1 + self.found = False + + def result(self, result): + if not self.found: + result = getAttribute(self.xf, '@@transducer/step')(result, -1) + return getAttribute(self.xf, '@@transducer/result')(result) + + def step(self, result, _input): + self.idx += 1 + if self.f(_input): + self.found = True + result = _reduced(getAttribute(self.xf, '@@transducer/step')(result, self.idx)) + return result + + +def _xfindIndex(f): return lambda xf: XFindIndex(f, xf) diff --git a/ramda/private/_xfindLast.py b/ramda/private/_xfindLast.py new file mode 100644 index 0000000..cb985ad --- /dev/null +++ b/ramda/private/_xfindLast.py @@ -0,0 +1,20 @@ +from ._helper import getAttribute +from ._xfBase import XfBase + + +class XFindLast(XfBase): + def __init__(self, f, xf): + self.xf = xf + self.f = f + self.last = None + + def result(self, result): + return getAttribute(self.xf, '@@transducer/result')(getAttribute(self.xf, '@@transducer/step')(result, self.last)) + + def step(self, result, _input): + if self.f(_input): + self.last = _input + return result + + +def _xfindLast(f): return lambda xf: XFindLast(f, xf) diff --git a/ramda/private/_xfindLastIndex.py b/ramda/private/_xfindLastIndex.py new file mode 100644 index 0000000..3c658d7 --- /dev/null +++ b/ramda/private/_xfindLastIndex.py @@ -0,0 +1,22 @@ +from ._helper import getAttribute +from ._xfBase import XfBase + + +class XFindLastIndex(XfBase): + def __init__(self, f, xf): + self.xf = xf + self.f = f + self.idx = -1 + self.lastIdx = -1 + + def result(self, result): + return getAttribute(self.xf, '@@transducer/result')(getAttribute(self.xf, '@@transducer/step')(result, self.lastIdx)) + + def step(self, result, _input): + self.idx += 1 + if self.f(_input): + self.lastIdx = self.idx + return result + + +def _xfindLastIndex(f): return lambda xf: XFindLastIndex(f, xf) diff --git a/test/test_findIndex.py b/test/test_findIndex.py new file mode 100644 index 0000000..6adb8d9 --- /dev/null +++ b/test/test_findIndex.py @@ -0,0 +1,45 @@ + +import unittest + +import ramda as R + +from .helpers.listXf import listXf + +""" +https://github.com/ramda/ramda/blob/master/test/findIndex.js +""" + +obj1 = {'x': 100} +obj2 = {'x': 200} +a = [11, 10, 9, 'cow', obj1, 8, 7, 100, 200, 300, obj2, 4, 3, 2, 1, 0] +def even(x): return x % 2 == 0 if isinstance(x, int) else False +def gt100(x): return x > 100 if isinstance(x, int) else False +def isStr(x): return isinstance(x, str) +def xGt100(o): return o['x'] > 100 if isinstance(o, dict) and isinstance(o['x'], int) else False + + +class TestFindIndex(unittest.TestCase): + def test_returns_the_index_of_the_first_element_that_satisfies_the_predicate(self): + self.assertEqual(1, R.findIndex(even, a)) + self.assertEqual(8, R.findIndex(gt100, a)) + self.assertEqual(3, R.findIndex(isStr, a)) + self.assertEqual(10, R.findIndex(xGt100, a)) + + def test_return_minus_one_when_no_element_satisfies_the_predicate(self): + self.assertEqual(-1, R.findIndex(even, ['zing'])) + self.assertEqual(-1, R.findIndex(even, [])) + + def test_dispatches_to_transformer_objects(self): + res = R.findIndex(R.identity, listXf) + self.assertEqual(R.identity, res.f) + self.assertEqual(False, res.found) + self.assertEqual(-1, res.idx) + self.assertEqual(listXf, res.xf) + + def test_can_act_as_a_transducer(self): + self.assertEqual([1], R.into([], R.findIndex(even), a)) + # TODO: transducer tests + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_findLast.py b/test/test_findLast.py new file mode 100644 index 0000000..4d3f0d0 --- /dev/null +++ b/test/test_findLast.py @@ -0,0 +1,46 @@ + +import unittest + +import ramda as R + +from .helpers.listXf import listXf + +""" +https://github.com/ramda/ramda/blob/master/test/findLast.js +""" + +obj1 = {'x': 100} +obj2 = {'x': 200} +a = [11, 10, 9, 'cow', obj1, 8, 7, 100, 200, 300, obj2, 4, 3, 2, 1, 0] +def even(x): return x % 2 == 0 if isinstance(x, int) else False +def gt100(x): return x > 100 if isinstance(x, int) else False +def isStr(x): return isinstance(x, str) +def xGt100(o): return o['x'] > 100 if isinstance(o, dict) and isinstance(o['x'], int) else False + + +class TestFindLast(unittest.TestCase): + def test_returns_the_index_of_the_last_element_that_satisfies_the_predicate(self): + self.assertEqual(0, R.findLast(even, a)) + self.assertEqual(300, R.findLast(gt100, a)) + self.assertEqual('cow', R.findLast(isStr, a)) + self.assertEqual(obj2, R.findLast(xGt100, a)) + + def test_return_None_when_no_element_satisfies_the_predicate(self): + self.assertEqual(None, R.findLast(even, ['zing'])) + self.assertEqual(None, R.findLast(even, [])) + + def test_works_when_the_first_element_matches(self): + self.assertEqual(2, R.findLast(even, [2, 3, 5])) + + def test_dispatches_to_transformer_objects(self): + res = R.findLast(R.identity, listXf) + self.assertEqual(R.identity, res.f) + self.assertEqual(listXf, res.xf) + + def test_can_act_as_a_transducer(self): + self.assertEqual([0], R.into([], R.findLast(even), a)) + # TODO: transducer tests + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_findLastIndex.py b/test/test_findLastIndex.py new file mode 100644 index 0000000..6904f03 --- /dev/null +++ b/test/test_findLastIndex.py @@ -0,0 +1,49 @@ + +import unittest + +import ramda as R + +from .helpers.listXf import listXf + +""" +https://github.com/ramda/ramda/blob/master/test/findLastIndex.js +""" + +obj1 = {'x': 100} +obj2 = {'x': 200} +a = [11, 10, 9, 'cow', obj1, 8, 7, 100, 200, 300, obj2, 4, 3, 2, 1, 0] +def even(x): return x % 2 == 0 if isinstance(x, int) else False +def gt100(x): return x > 100 if isinstance(x, int) else False +def isStr(x): return isinstance(x, str) +def xGt100(o): return o['x'] > 100 if isinstance(o, dict) and isinstance(o['x'], int) else False + + +class TestfindLastIndex(unittest.TestCase): + def test_returns_the_index_of_the_last_element_that_satisfies_the_predicate(self): + self.assertEqual(15, R.findLastIndex(even, a)) + self.assertEqual(9, R.findLastIndex(gt100, a)) + self.assertEqual(3, R.findLastIndex(isStr, a)) + self.assertEqual(10, R.findLastIndex(xGt100, a)) + + def test_return_minus_one_when_no_element_satisfies_the_predicate(self): + self.assertEqual(-1, R.findLastIndex(even, ['zing'])) + self.assertEqual(-1, R.findLastIndex(even, [])) + + def test_works_when_the_first_element_matches(self): + self.assertEqual(0, R.findLastIndex(even, [2, 3, 5])) + self.assertEqual(3, R.findLastIndex(even, [2, 3, 5, 6])) + + def test_dispatches_to_transformer_objects(self): + res = R.findLastIndex(R.identity, listXf) + self.assertEqual(R.identity, res.f) + self.assertEqual(-1, res.idx) + self.assertEqual(-1, res.lastIdx) + self.assertEqual(listXf, res.xf) + + def test_can_act_as_a_transducer(self): + self.assertEqual([15], R.into([], R.findLastIndex(even), a)) + # TODO: transducer tests + + +if __name__ == '__main__': + unittest.main()