Skip to content

Commit 20ad363

Browse files
committed
Merge remote-tracking branch 'origin/development' into development
2 parents 525b02a + 1dee096 commit 20ad363

File tree

7 files changed

+240
-31
lines changed

7 files changed

+240
-31
lines changed
File renamed without changes.

source_py3/python_toolbox/math_tools/misc.py

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
import numbers
55
import math
6+
import random
7+
8+
import python_toolbox.cute_enum
69

710

811
infinity = float('inf')
@@ -153,31 +156,69 @@ def is_integer(x):
153156
return inted_x == x
154157

155158

156-
def cute_round(x, step=1, up=False):
159+
class RoundMode(python_toolbox.cute_enum.CuteEnum):
160+
CLOSEST_OR_DOWN = 0
161+
CLOSEST_OR_UP = 1
162+
ALWAYS_DOWN = 2
163+
ALWAYS_UP = 3
164+
PROBABILISTIC = 4
165+
166+
def cute_round(x, round_mode=RoundMode.CLOSEST_OR_DOWN, *, step=1):
157167
'''
158-
Round with a chosen step.
159-
160-
Examples:
168+
Round a number, with lots of different options for rounding.
161169
170+
Basic usage:
171+
162172
>>> cute_round(7.456)
163173
7
164-
>>> cute_round(7.456, up=True)
165-
8
166-
>>> cute_round(7.456, step=0.1)
167-
7.4
168-
>>> cute_round(7.456, step=0.1, up=True)
169-
7.5
170-
>>> cute_round(7.456, step=0.2)
171-
7.4
172-
>>> cute_round(7.456, step=0.2, up=True)
173-
7.6
174-
>>> cute_round(7.456, step=0.01)
175-
7.45
176-
>>> cute_round(7.456, step=0.01, up=True)
177-
7.46
174+
175+
The optional `step=1` argument can be changed to change the definition of a
176+
round number. e.g., if you set `step=100`, then 1234 will be rounded to
177+
1200. `step` doesn't have to be an integer.
178+
179+
There are different rounding modes:
180+
181+
RoundMode.CLOSEST_OR_DOWN
182+
183+
Default mode: Round to the closest round number. If we're smack in
184+
the middle, like 4.5, round down to 4.
185+
186+
RoundMode.CLOSEST_OR_UP
187+
188+
Round to the closest round number. If we're smack in the middle,
189+
like 4.5, round up to 5.
190+
191+
RoundMode.ALWAYS_DOWN
192+
193+
Always round down. Even 4.99 gets rounded down to 4.
194+
195+
RoundMode.ALWAYS_UP
196+
197+
Always round up. Even 4.01 gets rounded up to 5.
198+
199+
RoundMode.PROBABILISTIC
200+
201+
Probabilistic round, giving a random result depending on how close
202+
the number is to each of the two surrounding round numbers. For
203+
example, if you round 4.5 with this mode, you'll get either 4 or 5
204+
with an equal probability. If you'll round 4.1 with this mode,
205+
there's a 90% chance you'll get 4, and a 10% chance you'll get 5.
206+
178207
179208
'''
209+
assert step > 0
180210
div, mod = divmod(x, step)
181-
return (div + bool(mod and up)) * step
211+
if round_mode == RoundMode.CLOSEST_OR_DOWN:
212+
round_up = (mod > 0.5 * step)
213+
elif round_mode == RoundMode.CLOSEST_OR_UP:
214+
round_up = (mod >= 0.5 * step)
215+
elif round_mode == RoundMode.ALWAYS_DOWN:
216+
round_up = False
217+
elif round_mode == RoundMode.ALWAYS_UP:
218+
round_up = True
219+
else:
220+
assert round_mode == RoundMode.PROBABILISTIC
221+
round_up = random.random() < mod / step
222+
return (div + round_up) * step
182223

183224

source_py3/python_toolbox/math_tools/statistics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ def get_mean(iterable):
2929
sum_ += value
3030
return sum_ / (i + 1)
3131

32-
32+

source_py3/python_toolbox/nifty_collections/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .various_frozen_dicts import FrozenDict, FrozenOrderedDict
1212
from .bagging import Bag, OrderedBag, FrozenBag, FrozenOrderedBag
1313
from .frozen_bag_bag import FrozenBagBag
14-
from .cute_enum import CuteEnum
14+
from ..cute_enum import CuteEnum
1515

1616
from .emitting_weak_key_default_dict import EmittingWeakKeyDefaultDict
1717

source_py3/python_toolbox/sequence_tools/misc.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,43 @@ def divide_to_slices(sequence, n_slices):
320320
return [sequence[x:y] for x, y in
321321
cute_iter_tools.iterate_overlapping_subsequences(indices)]
322322

323+
324+
def is_subsequence(big_sequence, small_sequence):
325+
'''
326+
Check whether `small_sequence` is a subsequence of `big_sequence`.
327+
328+
For example:
329+
330+
>>> is_subsequence([1, 2, 3, 4], [2, 3])
331+
True
332+
>>> is_subsequence([1, 2, 3, 4], [4, 5])
333+
False
334+
335+
This can be used on any kind of sequence, including tuples, lists and
336+
strings.
337+
'''
338+
from python_toolbox import nifty_collections
339+
big_sequence = ensure_iterable_is_sequence(big_sequence,
340+
allow_unordered=False)
341+
small_sequence = ensure_iterable_is_sequence(small_sequence,
342+
allow_unordered=False)
343+
small_sequence_length = len(small_sequence)
344+
last_index_that_subsequence_can_start = \
345+
len(big_sequence) - len(small_sequence) + 1
346+
matches = {}
347+
for i, item in enumerate(big_sequence):
348+
if matches:
349+
new_matches = {}
350+
for match_position, match_length in matches.items():
351+
if small_sequence[match_length] == item:
352+
new_matches[match_position] = match_length + 1
353+
matches = new_matches
354+
if (item == small_sequence[0]) and \
355+
(i < last_index_that_subsequence_can_start):
356+
matches[i] = 1
357+
for match_position, match_length in matches.items():
358+
if match_length == small_sequence_length:
359+
return True
360+
361+
323362

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,107 @@
11
# Copyright 2009-2015 Ram Rachum.
22
# This program is distributed under the MIT license.
33

4-
from python_toolbox.math_tools import cute_round
4+
import inspect
5+
6+
from python_toolbox import nifty_collections
7+
8+
from python_toolbox.math_tools import cute_round, RoundMode
59

610
def almost_equals(x, y):
711
return (abs(1-(x / y)) < (10 ** -10))
812

913

10-
def test_cute_round():
11-
assert almost_equals(cute_round(7.456), 7)
12-
assert almost_equals(cute_round(7.456, up=True), 8)
13-
assert almost_equals(cute_round(7.456, step=0.1), 7.4)
14-
assert almost_equals(cute_round(7.456, step=0.1, up=True), 7.5)
15-
assert almost_equals(cute_round(7.456, step=0.2), 7.4)
16-
assert almost_equals(cute_round(7.456, step=0.2, up=True), 7.6)
17-
assert almost_equals(cute_round(7.456, step=0.01), 7.45)
18-
assert almost_equals(cute_round(7.456, step=0.01, up=True), 7.46)
14+
class CuteRoundTestCase:
15+
def test_closest_or_down(self):
16+
full_arg_spec = inspect.getfullargspec(cute_round)
17+
assert RoundMode.CLOSEST_OR_DOWN in full_arg_spec.defaults
18+
19+
assert almost_equals(cute_round(7.456), 7)
20+
assert almost_equals(cute_round(7.654), 8)
21+
assert almost_equals(cute_round(7.5), 7)
22+
assert almost_equals(cute_round(7.456, step=0.1), 7.5)
23+
assert almost_equals(cute_round(7.456, step=0.2), 7.4)
24+
assert almost_equals(cute_round(7.456, step=0.01), 7.46)
25+
26+
def test_closest_or_up(self):
27+
assert almost_equals(
28+
cute_round(7.456, RoundMode.CLOSEST_OR_UP), 7
29+
)
30+
assert almost_equals(
31+
cute_round(7.654, RoundMode.CLOSEST_OR_UP), 8
32+
)
33+
assert almost_equals(
34+
cute_round(7.5, RoundMode.CLOSEST_OR_UP), 8
35+
)
36+
assert almost_equals(
37+
cute_round(7.456, RoundMode.CLOSEST_OR_UP, step=0.1), 7.5
38+
)
39+
assert almost_equals(
40+
cute_round(7.456, RoundMode.CLOSEST_OR_UP, step=0.2), 7.4
41+
)
42+
assert almost_equals(
43+
cute_round(7.456, RoundMode.CLOSEST_OR_UP, step=0.01), 7.46
44+
)
45+
46+
def test_always_up(self):
47+
assert almost_equals(
48+
cute_round(7.456, RoundMode.ALWAYS_UP), 8
49+
)
50+
assert almost_equals(
51+
cute_round(7.654, RoundMode.ALWAYS_UP), 8
52+
)
53+
assert almost_equals(
54+
cute_round(7.5, RoundMode.ALWAYS_UP), 8
55+
)
56+
assert almost_equals(
57+
cute_round(7.456, RoundMode.ALWAYS_UP, step=0.1), 7.5
58+
)
59+
assert almost_equals(
60+
cute_round(7.456, RoundMode.ALWAYS_UP, step=0.2), 7.6
61+
)
62+
assert almost_equals(
63+
cute_round(7.456, RoundMode.ALWAYS_UP, step=0.01), 7.46
64+
)
65+
66+
def test_always_down(self):
67+
assert almost_equals(
68+
cute_round(7.456, RoundMode.ALWAYS_DOWN), 7
69+
)
70+
assert almost_equals(
71+
cute_round(7.654, RoundMode.ALWAYS_DOWN), 7
72+
)
73+
assert almost_equals(
74+
cute_round(7.5, RoundMode.ALWAYS_DOWN), 7
75+
)
76+
assert almost_equals(
77+
cute_round(7.456, RoundMode.ALWAYS_DOWN, step=0.1), 7.4
78+
)
79+
assert almost_equals(
80+
cute_round(7.456, RoundMode.ALWAYS_DOWN, step=0.2), 7.4
81+
)
82+
assert almost_equals(
83+
cute_round(7.456, RoundMode.ALWAYS_DOWN, step=0.01), 7.45
84+
)
85+
86+
def test_probabilistic(self):
87+
def get_bag(*args, **kwargs):
88+
kwargs.update({'round_mode': RoundMode.PROBABILISTIC,})
89+
return nifty_collections.Bag(
90+
cute_round(*args, **kwargs) for i in range(1000)
91+
)
92+
93+
bag = get_bag(5, step=5)
94+
assert bag[5] == 1000
95+
96+
bag = get_bag(6, step=5)
97+
assert 300 <= bag[5] <= 908
98+
assert 2 <= bag[10] <= 600
99+
100+
bag = get_bag(7.5, step=5)
101+
assert 100 <= bag[5] <= 900
102+
assert 100 <= bag[10] <= 900
103+
104+
bag = get_bag(10, step=5)
105+
assert bag[10] == 1000
106+
107+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2009-2015 Ram Rachum.
2+
# This program is distributed under the MIT license.
3+
4+
from python_toolbox import cute_testing
5+
from python_toolbox import sequence_tools
6+
from python_toolbox.sequence_tools import is_subsequence
7+
8+
9+
10+
def test():
11+
true_pairs = (
12+
([1, 2, 3, 4], [2, 3]),
13+
([1, 2, 3, 4], (2, 3)),
14+
([1, 2, 'meow', 3, 4], (2, 'meow', 3)),
15+
('abracadabra', 'cad'),
16+
('abracadabra', 'dab'),
17+
('abracadabra', 'a'),
18+
('abracadabra', 'ab'),
19+
('abracadabra', 'bra'),
20+
(range(10000), (range(7, 14))),
21+
(range(10000), [99]),
22+
)
23+
false_pairs = (
24+
([1, 2, 3, 4], [2, 4]),
25+
([1, 2, 3, 4], (2, 4)),
26+
([1, 2, 'meow', 3, 4], (2, 3)),
27+
('abracadabra', 'cab'),
28+
('abracadabra', 'darb'),
29+
('abracadabra', 'z'),
30+
('abracadabra', 'bab'),
31+
('abracadabra', 'arb'),
32+
(range(10000), (range(14, 7, -1))),
33+
(range(100), [100]),
34+
(range(100), [109]),
35+
)
36+
37+
for true_pair in true_pairs:
38+
assert is_subsequence(*true_pair)
39+
for false_pair in false_pairs:
40+
assert not is_subsequence(*false_pair)

0 commit comments

Comments
 (0)