diff --git a/README.md b/README.md index b406fb0..23c2400 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,47 @@ R.keysIn({'a': 1, 'b': 2}) # ['a', 'b'] - [x] 0.1.4 last - [x] 0.1.2 lastIndexOf -- [ ] length +- [x] length + +The behavior of `length` is different from `ramda`. + +```python +# Array +R.length([1, 2, 3]) # 3 +# String +R.length('abc') # 3 +# Dict +R.length({'a': 1, 'b': 2}) # 2 +# Set +R.length({1, 2, 3}) # 3 +# Tuple +R.length((1, 2, 3)) # 3 +# Notice: Also works for any other iterable object + +# Some special cases +# object with length() method +class Obj: + def length(self): + return 3 +obj = Obj() +R.length(obj) # 3 + +# dict with length property +R.length({'a': 1, 'length': 99}) # 99, R.length will use length property instead + +# return function arguments length +def f(a, b, c): + return a + b + c +R.length(f) # 3 + +# Any failed cases, return nan instead +R.length(None) # float('nan') +R.length(1) # float('nan') +class ObjWithoutLength: + pass +R.length(ObjWithoutLength()) # float('nan') +``` + - [ ] lens - [ ] lensIndex - [ ] lensPath diff --git a/ramda/__init__.py b/ramda/__init__.py index a6f64a2..d8f2dee 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -49,6 +49,7 @@ from .keysIn import keysIn from .last import last from .lastIndexOf import lastIndexOf +from .length import length from .lt import lt from .lte import lte from .map import map diff --git a/ramda/length.py b/ramda/length.py new file mode 100644 index 0000000..0854ce4 --- /dev/null +++ b/ramda/length.py @@ -0,0 +1,30 @@ +from .private._curry1 import _curry1 +from .private._has import _has +from .private._helper import getAttribute +from .private._inspect import funcArgsLength +from .private._isFunction import _isFunction +from .private._isNumber import _isNumber + + +def number_or_nan(x): + if _isNumber(x): + return x + return float('nan') + + +def inner_length(x): + if x is None: + return float('nan') + if _isFunction(x): + return funcArgsLength(x) + if _isFunction(getAttribute(x, 'length')): + return number_or_nan(getAttribute(x, 'length')()) + if _has(x, 'length'): + return number_or_nan(getAttribute(x, 'length')) + try: + return len(x) + except TypeError: + return float('nan') + + +length = _curry1(inner_length) diff --git a/test/test_length.py b/test/test_length.py new file mode 100644 index 0000000..8099380 --- /dev/null +++ b/test/test_length.py @@ -0,0 +1,77 @@ + +import unittest +from math import isnan + +import ramda as R + +""" +https://github.com/ramda/ramda/blob/master/test/length.js +""" + + +class TestLength(unittest.TestCase): + def test_returns_the_length_of_a_list(self): + self.assertEqual(0, R.length([])) + self.assertEqual(4, R.length(['a', 'b', 'c', 'd'])) + + def test_returns_the_length_of_a_string(self): + self.assertEqual(0, R.length('')) + self.assertEqual(3, R.length('xyz')) + + def test_returns_the_length_of_a_function(self): + self.assertEqual(0, R.length(lambda: None)) + self.assertEqual(3, R.length(lambda x, y, z: z)) + + def test_returns_the_length_of_a_dict(self): + self.assertEqual(0, R.length({})) + self.assertEqual(2, R.length({'a': 1, 'b': 2})) + + def test_returns_the_length_of_an_arguments_object(self): + fn = lambda *args: args + self.assertEqual(0, R.length(fn())) + self.assertEqual(3, R.length(fn(1, 2, 3))) + + def test_returns_the_length_of_a_set(self): + self.assertEqual(0, R.length(set())) + self.assertEqual(3, R.length(set([1, 2, 3]))) + self.assertEqual(3, R.length({1, 2, 3})) + + def test_returns_the_length_of_a_tuple(self): + self.assertEqual(0, R.length(())) + self.assertEqual(3, R.length((1, 2, 3))) + + def test_returns_nan_for_value_of_unexpected_type(self): + self.assertTrue(isnan(R.length(0))) + self.assertTrue(isnan(R.length(None))) + + def test_returns_nan_for_length_property_of_unexpected_type(self): + self.assertTrue(isnan(R.length({'length': ''}))) + self.assertTrue(isnan(R.length({'length': '1.23'}))) + self.assertTrue(isnan(R.length({'length': None}))) + + class ObjWithoutLength: + pass + obj = ObjWithoutLength() + self.assertTrue(isnan(R.length(obj))) + + def test_use_length_property(self): + self.assertEqual(100, R.length({'length': 100})) + self.assertEqual(100, R.length({'a': None, 'length': 100})) + + def test_dispatches_to_object_length(self): + class Obj: + def length(self): + return 100 + obj = Obj() + self.assertEqual(100, R.length(obj)) + + def test_dispatches_to_object_length_return_nan_if_unexpected_type(self): + class Obj: + def length(self): + return '1.23' + obj = Obj() + self.assertTrue(isnan(R.length(obj))) + + +if __name__ == '__main__': + unittest.main()