Skip to content

Commit b255683

Browse files
committed
Add latest methods and their tests
ADD: `where`, `findWhere`, `pairs`, `invert`, `partial`, `indexBy` and `random` ADD: tests for these new methods FIX: return object instead of list if there is only one item `groupBy` FIX: timing on async tests
1 parent 1b221bb commit b255683

File tree

6 files changed

+182
-18
lines changed

6 files changed

+182
-18
lines changed

src/underscore.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from itertools import ifilterfalse
55
import re
66
import functools
7+
import random
78
from sets import Set
89
from threading import Timer
910

@@ -314,6 +315,34 @@ def pluck(self, key):
314315
"""
315316
return self._wrap([x.get(key) for x in self.obj])
316317

318+
def where(self, attrs=None, first=False):
319+
"""
320+
Convenience version of a common use case of `filter`: selecting only objects
321+
containing specific `key:value` pairs.
322+
"""
323+
if attrs is None:
324+
return None if first is True else []
325+
326+
method = _.find if first else _.filter
327+
328+
def by(val, *args):
329+
for key, value in attrs.items():
330+
try:
331+
if attrs[key] != val[key]:
332+
return False
333+
except KeyError:
334+
return False
335+
return True
336+
337+
return self._wrap(method(self.obj, by))
338+
339+
def findWhere(self, attrs=None):
340+
"""
341+
Convenience version of a common use case of `find`: getting the first object
342+
containing specific `key:value` pairs.
343+
"""
344+
return self._wrap(self._clean.where(attrs, True))
345+
317346
def max(self):
318347
""" Return the maximum element or (element-based computation).
319348
"""
@@ -335,7 +364,7 @@ def shuffle(self):
335364
return self._wrap(list())
336365

337366
cloned = self.obj[:]
338-
import random
367+
339368
random.shuffle(cloned)
340369
return self._wrap(cloned)
341370

@@ -369,6 +398,8 @@ def e(value, index, *args):
369398

370399
_.each(obj, e)
371400

401+
if len(ns.result) == 1:
402+
return ns.result[0]
372403
return ns.result
373404

374405
def groupBy(self, val):
@@ -386,6 +417,21 @@ def by(result, key, value):
386417

387418
return self._wrap(res)
388419

420+
def indexBy(self, val=None):
421+
"""
422+
Indexes the object's values by a criterion, similar to `groupBy`, but for
423+
when you know that your index values will be unique.
424+
"""
425+
if val is None:
426+
val = lambda *args: args[0]
427+
428+
def by(result, key, value):
429+
result[key] = value
430+
431+
res = self._group(self.obj, val, by)
432+
433+
return self._wrap(res)
434+
389435
def countBy(self, val):
390436
"""
391437
Counts instances of an object that group by a certain criterion. Pass
@@ -676,6 +722,17 @@ def bind(self, context):
676722
return self._wrap(self.obj)
677723
curry = bind
678724

725+
def partial(self, *args):
726+
"""
727+
Partially apply a function by creating a version that has had some of its
728+
arguments pre-filled, without changing its dynamic `this` context.
729+
"""
730+
def part(*args2):
731+
args3 = args + args2
732+
return self.obj(*args3)
733+
734+
return self._wrap(part)
735+
679736
def bindAll(self, *args):
680737
"""
681738
Bind all of an object's methods to that object.
@@ -863,6 +920,26 @@ def values(self):
863920
"""
864921
return self._wrap(self.obj.values())
865922

923+
def pairs(self):
924+
""" Convert an object into a list of `[key, value]` pairs.
925+
"""
926+
keys = self._clean.keys()
927+
pairs = []
928+
for key in keys:
929+
pairs.append([key, self.obj[key]])
930+
931+
return self._wrap(pairs)
932+
933+
def invert(self):
934+
""" Invert the keys and values of an object. The values must be serializable.
935+
"""
936+
keys = self._clean.keys()
937+
inverted = {}
938+
for key in keys:
939+
inverted[self.obj[key]] = key
940+
941+
return self._wrap(inverted)
942+
866943
def functions(self):
867944
""" Return a sorted list of the function names available on the object.
868945
"""
@@ -1176,6 +1253,15 @@ def times(self, func, *args):
11761253

11771254
return self._wrap(func)
11781255

1256+
def random(self, max_number=None):
1257+
""" Return a random integer between min and max (inclusive).
1258+
"""
1259+
min_number = self.obj
1260+
if max_number is None:
1261+
min_number = 0
1262+
max_number = self.obj
1263+
return random.randrange(min_number, max_number)
1264+
11791265
def result(self, property, *args):
11801266
"""
11811267
If the value of the named property is a function then invoke it;

tests/test_collections.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,51 @@ def test_size(self):
182182
self.assertEqual(_.size({"one": 1, "two": 2, "three": 3}), 3, 'can compute the size of an object')
183183
self.assertEqual(_.size([1, 2, 3]), 3, 'can compute the size of an array')
184184

185+
def test_where(self):
186+
List = [{"a": 1, "b": 2}, {"a": 2, "b": 2}, {"a": 1, "b": 3}, {"a": 1, "b": 4}]
187+
result = _.where(List, {"a": 1})
188+
self.assertEqual(_.size(result), 3)
189+
self.assertEqual(result[-1]['b'], 4)
190+
191+
result = _.where(List, {"a": 1}, True)
192+
self.assertEqual(result["b"], 2)
193+
194+
result = _.where(List, {"a": 1}, False)
195+
self.assertEqual(_.size(result), 3)
196+
197+
def test_findWhere(self):
198+
List = [{"a": 1, "b": 2}, {"a": 2, "b": 2}, {"a": 1, "b": 3}, {"a": 1, "b": 4}]
199+
result = _.findWhere(List, {"a": 1})
200+
self.assertEqual(result["a"], 1)
201+
self.assertEqual(result["b"], 2)
202+
203+
result = _.findWhere(List, {"b": 4})
204+
self.assertEqual(result["a"], 1)
205+
self.assertEqual(result["b"], 4)
206+
207+
result = _.findWhere(List, {"c": 1})
208+
self.assertEqual(result, None)
209+
210+
result = _.findWhere([], {"c": 1})
211+
self.assertEqual(result, None)
212+
213+
def test_indexBy(self):
214+
parity = _.indexBy([1, 2, 3, 4, 5], lambda num, *args: num % 2 == 0)
215+
self.assertEqual(parity[True], 4)
216+
self.assertEqual(parity[False], 5)
217+
218+
llist = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]
219+
grouped = _.indexBy(llist, lambda x, *args: len(x))
220+
self.assertEqual(grouped[3], 'ten')
221+
self.assertEqual(grouped[4], 'nine')
222+
self.assertEqual(grouped[5], 'eight')
223+
224+
array = [1, 2, 1, 2, 3]
225+
grouped = _.indexBy(array);
226+
self.assertEqual(grouped[1], 1)
227+
self.assertEqual(grouped[2], 2)
228+
self.assertEqual(grouped[3], 3)
229+
185230
if __name__ == "__main__":
186-
print "run these tests by executing `python -m unittest discover` in unittests folder"
231+
print ("run these tests by executing `python -m unittest discover` in unittests folder")
187232
unittest.main()

tests/test_functions.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@ def test_delay(self):
4141
def func():
4242
ns.delayed = True
4343

44-
_.delay(func, 50)
44+
_.delay(func, 150)
4545

4646
def checkFalse():
4747
self.assertFalse(ns.delayed)
48-
print "ASYNC: delay. OK"
48+
print ("\nASYNC: delay. OK")
4949

5050
def checkTrue():
5151
self.assertTrue(ns.delayed)
52-
print "ASYNC: delay. OK"
52+
print ("\nASYNC: delay. OK")
5353

54-
Timer(0.03, checkFalse).start()
55-
Timer(0.07, checkTrue).start()
54+
Timer(0.05, checkFalse).start()
55+
Timer(0.20, checkTrue).start()
5656

5757
def test_defer(self):
5858
ns = self.Namespace()
@@ -65,7 +65,7 @@ def defertTest(bool):
6565

6666
def deferCheck():
6767
self.assertTrue(ns.deferred, "deferred the function")
68-
print "ASYNC: defer. OK"
68+
print ("\nASYNC: defer. OK")
6969

7070
_.delay(deferCheck, 50)
7171

@@ -85,15 +85,15 @@ def incr():
8585
Timer(0.14, throttledIncr).start()
8686
Timer(0.19, throttledIncr).start()
8787
Timer(0.22, throttledIncr).start()
88-
Timer(0.24, throttledIncr).start()
88+
Timer(0.34, throttledIncr).start()
8989

9090
def checkCounter1():
9191
self.assertEqual(ns.counter, 1, "incr was called immediately")
92-
print "ASYNC: throttle. OK"
92+
print ("ASYNC: throttle. OK")
9393

9494
def checkCounter2():
9595
self.assertEqual(ns.counter, 4, "incr was throttled")
96-
print "ASYNC: throttle. OK"
96+
print ("ASYNC: throttle. OK")
9797

9898
_.delay(checkCounter1, 90)
9999
_.delay(checkCounter2, 400)
@@ -105,7 +105,7 @@ def test_debounce(self):
105105
def incr():
106106
ns.counter += 1
107107

108-
debouncedIncr = _.debounce(incr, 50)
108+
debouncedIncr = _.debounce(incr, 120)
109109
debouncedIncr()
110110
debouncedIncr()
111111
debouncedIncr()
@@ -117,9 +117,9 @@ def incr():
117117

118118
def checkCounter():
119119
self.assertEqual(1, ns.counter, "incr was debounced")
120-
print "ASYNC: debounce. OK"
120+
print ("ASYNC: debounce. OK")
121121

122-
_.delay(checkCounter, 220)
122+
_.delay(checkCounter, 300)
123123

124124
def test_once(self):
125125
ns = self.Namespace()
@@ -187,6 +187,12 @@ def afterFunc():
187187
self.assertEqual(testAfter(5, 4), 0, "after(N) should not fire unless called N times")
188188
self.assertEqual(testAfter(0, 0), 1, "after(0) should fire immediately")
189189

190+
def test_partial(self):
191+
def func(*args):
192+
return ' '.join(args)
193+
pfunc = _.partial(func, 'a', 'b', 'c')
194+
self.assertEqual(pfunc('d', 'e'), 'a b c d e')
195+
190196
if __name__ == "__main__":
191-
print "run these tests by executing `python -m unittest discover` in unittests folder"
197+
print ("run these tests by executing `python -m unittest discover` in unittests folder")
192198
unittest.main()

tests/test_objects.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ def interceptor(obj):
122122
returned = _([1, 2, 3]).chain().map(lambda n, *args: n * 2).max().tap(interceptor).value()
123123
self.assertTrue(returned == 6 and ns.intercepted == 6, 'can use tapped objects in a chain')
124124

125+
def test_pairs(self):
126+
r = _.pairs({"one": 1, "two": 2})
127+
# Python alphabetically orders the keys automatically
128+
self.assertEqual(r, [["two", 2], ["one", 1]], 'can convert an object into pairs')
129+
130+
def test_invert(self):
131+
obj = {"first": 'Moe', "second": 'Larry', "third": 'Curly'}
132+
r = _(obj).chain().invert().keys().join(' ').value()
133+
self.assertEqual(r, 'Larry Moe Curly', 'can invert an object')
134+
self.assertEqual(_.invert(_.invert(obj)), obj, "two inverts gets you back where you started")
135+
125136
if __name__ == "__main__":
126-
print "run these tests by executing `python -m unittest discover` in unittests folder"
137+
print ("run these tests by executing `python -m unittest discover` in unittests folder")
127138
unittest.main()

tests/test_structure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ def test_chaining(self):
2121
self.assertEqual(6, u.value(), "value should have returned")
2222

2323
if __name__ == "__main__":
24-
print "run these tests by executing `python -m unittest discover` in unittests folder"
24+
print ("run these tests by executing `python -m unittest discover` in unittests folder")
2525
unittest.main()

tests/test_utility.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from unittesthelper import init
33
init() # will let you import modules from upper folder
44
from src.underscore import _
5+
import math
56

67

78
class TestUtility(unittest.TestCase):
@@ -16,6 +17,21 @@ def test_identity(self):
1617
moe = {"name": 'moe'}
1718
self.assertEqual(moe, _.identity(moe), "moe is the same as his identity")
1819

20+
def test_random(self):
21+
array = _.range(1000)
22+
mi = math.pow(2, 31)
23+
ma = math.pow(2, 62)
24+
def check(*args):
25+
return _.random(mi, ma) >= mi
26+
result = _.every(array, check)
27+
self.assertTrue(result, "should produce a random number greater than or equal to the minimum number")
28+
29+
def check2(*args):
30+
r = _.random(ma)
31+
return r >= 0 and r <= ma
32+
result = _.every(array, check2)
33+
self.assertTrue(result, "should produce a random number when passed max_number")
34+
1935
def test_uniqueId(self):
2036
ns = self.Namespace()
2137
ns.ids = []
@@ -204,5 +220,5 @@ def test2():
204220
templateEscaped({"f": test2})
205221

206222
if __name__ == "__main__":
207-
print "run these tests by executing `python -m unittest discover` in unittests folder"
223+
print ("run these tests by executing `python -m unittest discover` in unittests folder")
208224
unittest.main()

0 commit comments

Comments
 (0)