diff --git a/.vscode/settings.json b/.vscode/settings.json index c5f64d2..3322830 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,9 +4,11 @@ "--ignore=E111, E114, E402, E501" ], "python.linting.pylintEnabled": true, - "python.linting.cwd": "{workspaceFolder}/ramda", "python.linting.enabled": true, + "python.linting.ignorePatterns": [ + "test/**" + ], "python.linting.pylintArgs": [ - "--rcfile=pylintrc" + "--rcfile=pylintrc", ] } diff --git a/README.md b/README.md index c479787..5014aea 100644 --- a/README.md +++ b/README.md @@ -482,11 +482,11 @@ Partially supported - [ ] unary - [ ] uncurryN - [ ] unfold -- [ ] union -- [ ] unionWith +- [x] union +- [x] unionWith - [x] uniq - [x] uniqBy -- [ ] uniqWith +- [x] uniqWith - [ ] unless - [ ] unnest - [ ] until diff --git a/ramda/__init__.py b/ramda/__init__.py index af71ab9..3af7a9f 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -81,6 +81,9 @@ from .take import take from .takeWhile import takeWhile from .toString import toString +from .union import union +from .unionWith import unionWith from .uniq import uniq from .uniqBy import uniqBy +from .uniqWith import uniqWith from .useWith import useWith diff --git a/ramda/private/_xuniqWith.py b/ramda/private/_xuniqWith.py new file mode 100644 index 0000000..f11e537 --- /dev/null +++ b/ramda/private/_xuniqWith.py @@ -0,0 +1,19 @@ +from ._helper import getAttribute +from ._includesWith import _includesWith +from ._xfBase import XfBase + + +class XUniqWith(XfBase): + def __init__(self, pred, xf): + self.xf = xf + self.pred = pred + self.items = [] + + def step(self, result, _input): + if _includesWith(self.pred, _input, self.items): + return result + self.items.append(_input) + return getAttribute(self.xf, '@@transducer/step')(result, _input) + + +def _xuniqWith(pred): return lambda xf: XUniqWith(pred, xf) diff --git a/ramda/union.py b/ramda/union.py new file mode 100644 index 0000000..803e526 --- /dev/null +++ b/ramda/union.py @@ -0,0 +1,6 @@ +from .compose import compose +from .private._concat import _concat +from .private._curry2 import _curry2 +from .uniq import uniq + +union = _curry2(compose(uniq, _concat)) diff --git a/ramda/unionWith.py b/ramda/unionWith.py new file mode 100644 index 0000000..83a8ab1 --- /dev/null +++ b/ramda/unionWith.py @@ -0,0 +1,5 @@ +from .private._concat import _concat +from .private._curry3 import _curry3 +from .uniqWith import uniqWith + +unionWith = _curry3(lambda pred, list1, list2: uniqWith(pred, _concat(list1, list2))) diff --git a/ramda/uniqWith.py b/ramda/uniqWith.py new file mode 100644 index 0000000..d7613f2 --- /dev/null +++ b/ramda/uniqWith.py @@ -0,0 +1,19 @@ +from .private._curry2 import _curry2 +from .private._dispatchable import _dispatchable +from .private._includesWith import _includesWith +from .private._xuniqWith import _xuniqWith + + +def inner_uniqWith(pred, arr): + idx = 0 + length = len(arr) + result = [] + while idx < length: + item = arr[idx] + if not _includesWith(pred, item, result): + result.append(item) + idx += 1 + return result + + +uniqWith = _curry2(_dispatchable([], _xuniqWith, inner_uniqWith)) diff --git a/test/test_union.py b/test/test_union.py new file mode 100644 index 0000000..7b1db2a --- /dev/null +++ b/test/test_union.py @@ -0,0 +1,27 @@ + +import unittest + +import ramda as R + +from .helpers.Maybe import Just + +""" +https://github.com/ramda/ramda/blob/master/test/union.js +""" + +M = [1, 2, 3, 4] +N = [3, 4, 5, 6] + + +class TestUnion(unittest.TestCase): + def test_combines_two_lists_into_the_set_of_all_their_elements(self): + self.assertEqual([1, 2, 3, 4, 5, 6], R.union(M, N)) + + def test_has_R_equals_semantics(self): + # TODO: ignore neg-zero and pos-zero check for now, due to simlicity + self.assertEqual(1, len(R.union([float('nan')], [float('nan')]))) + self.assertEqual(1, len(R.union([Just([42])], [Just([42])]))) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_unionWith.py b/test/test_unionWith.py new file mode 100644 index 0000000..7a3047f --- /dev/null +++ b/test/test_unionWith.py @@ -0,0 +1,24 @@ + +import unittest + +import ramda as R + +""" +https://github.com/ramda/ramda/blob/master/test/unionWith.js +""" + +Ro = [{'a': 1}, {'a': 2}, {'a': 3}, {'a': 4}] +So = [{'a': 3}, {'a': 4}, {'a': 5}, {'a': 6}] + + +def eqA(r, s): + return r['a'] == s['a'] + + +class TestUnionWith(unittest.TestCase): + def test_combines_two_lists_into_the_set_of_all_their_elements_based_on_the_passed_in_quiality_predicate(self): + self.assertEqual([{'a': 1}, {'a': 2}, {'a': 3}, {'a': 4}, {'a': 5}, {'a': 6}], R.unionWith(eqA, Ro, So)) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_uniqWith.py b/test/test_uniqWith.py new file mode 100644 index 0000000..da530d1 --- /dev/null +++ b/test/test_uniqWith.py @@ -0,0 +1,43 @@ + +import unittest + +import ramda as R + +""" +https://github.com/ramda/ramda/blob/master/test/uniqWith.js +""" + +objs = [ + {'x': R.T, 'i': 0}, {'x': R.F, 'i': 1}, {'x': R.T, 'i': 2}, {'x': R.T, 'i': 3}, + {'x': R.F, 'i': 4}, {'x': R.F, 'i': 5}, {'x': R.T, 'i': 6}, {'x': R.F, 'i': 7}, +] +objs2 = [ + {'x': R.T, 'i': 0}, {'x': R.F, 'i': 1}, {'x': R.T, 'i': 2}, {'x': R.T, 'i': 3}, + {'x': R.F, 'i': 0}, {'x': R.T, 'i': 1}, {'x': R.F, 'i': 2}, {'x': R.F, 'i': 3}, +] + + +def eqI(x, accX): + return x['i'] == accX['i'] + + +class TestUniqWith(unittest.TestCase): + def test_returns_a_set_from_any_array_based_on_predicate(self): + self.assertEqual(objs, R.uniqWith(eqI, objs)) + self.assertEqual([{'x': R.T, 'i': 0}, {'x': R.F, 'i': 1}, {'x': R.T, 'i': 2}, {'x': R.T, 'i': 3}], R.uniqWith(eqI, objs2)) + + def test_keeps_element_from_the_left(self): + self.assertEqual([{'i': 1}, {'i': 2}, {'i': 3}, {'i': 4}], R.uniqWith(eqI, [{'i': 1}, {'i': 2}, {'i': 3}, {'i': 4}, {'i': 1}])) + + def test_returns_an_empty_array_for_an_empty_array(self): + self.assertEqual([], R.uniqWith(eqI, [])) + + def test_can_act_as_a_transducer(self): + input = [1, '1', 2, 1] + expected = [1, 2] + # TODO: eqBy + # TODO: transduce + + +if __name__ == '__main__': + unittest.main()