Skip to content

Commit c251cb7

Browse files
committed
-
1 parent 8c94ac8 commit c251cb7

File tree

11 files changed

+142
-76
lines changed

11 files changed

+142
-76
lines changed

source_py3/python_toolbox/cute_iter_tools.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ def are_equal(*sequences, easy_types=(sequence_tools.CuteRange,)):
436436
# Trying cheap comparison:
437437
if len(sequence_types) == 1 and issubclass(
438438
get_single_if_any(sequence_types), easy_types):
439-
return logic_tools.all_equal(sequences)
439+
return logic_tools.all_equivalent(sequences)
440440

441441
# If cheap comparison didn't work, trying item-by-item comparison:
442442
zipped = itertools.zip_longest(*sequences,
@@ -445,7 +445,7 @@ def are_equal(*sequences, easy_types=(sequence_tools.CuteRange,)):
445445
# No need to explicitly check for `_EMPTY_SENTINEL`, it would just make
446446
# the following condition `False`, because it's impossible for all
447447
# values to be the sentinel.
448-
if not logic_tools.all_equal(values):
448+
if not logic_tools.all_equivalent(values):
449449
return False
450450
else:
451451
return True
@@ -551,7 +551,7 @@ def zip_non_equal(iterables, lazy_tuple=False):
551551
'''
552552
from python_toolbox import logic_tools
553553
iterator = (items for items in zip(*iterables)
554-
if not logic_tools.all_equal(items))
554+
if not logic_tools.all_equivalent(items))
555555

556556
if lazy_tuple:
557557
from python_toolbox import nifty_collections

source_py3/python_toolbox/cute_testing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def manage_context(self):
110110
def assert_same_signature(*callables):
111111
'''Assert that all the `callables` have the same function signature.'''
112112
arg_specs = [cute_inspect.getargspec(callable_) for callable_ in callables]
113-
if not logic_tools.all_equal(arg_specs, exhaustive=True):
113+
if not logic_tools.all_equivalent(arg_specs, exhaustive=True):
114114
raise Failure('Not all the callables have the same signature.')
115115

116116

source_py3/python_toolbox/logic_tools.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@
44
'''This module defines logic-related tools.'''
55

66
import collections
7+
import itertools
8+
import operator
79

810
from python_toolbox import cute_iter_tools
911

1012

11-
def all_equal(iterable, exhaustive=False):
13+
def all_equivalent(iterable, relation=operator.eq, *, exhaustive=False):
1214
'''
13-
Return whether all elements in the iterable are equal to each other.
15+
Return whether all elements in the iterable are equivalent to each other.
16+
17+
By default "equivalent" means they're equal to each other in Python. You
18+
can set a different relation to the `relation` argument, as a function that
19+
accepts two arguments and returns whether they're equivalent or not. You
20+
can use this, for example, to test if all items are NOT equal by passing in
21+
`relation=operator.ne`.
1422
1523
If `exhaustive` is set to `False`, it's assumed that the equality relation
1624
is transitive, therefore not every member is tested against every other
@@ -36,7 +44,7 @@ def all_equal(iterable, exhaustive=False):
3644
else: # exhaustive is False
3745
pairs = cute_iter_tools.iterate_overlapping_subsequences(iterable)
3846

39-
return all(a == b for (a, b) in pairs)
47+
return all(itertools.starmap(relation, pairs))
4048

4149

4250
def get_equivalence_classes(iterable, key=None, container=set,

source_py3/python_toolbox/nifty_collections/bagging.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from .lazy_tuple import LazyTuple
1616
from .ordered_dict import OrderedDict
17-
from .various_ordered_sets import OrderedSet
17+
from .various_ordered_sets import FrozenOrderedSet
1818
from .various_frozen_dicts import FrozenDict, FrozenOrderedDict
1919
from .abstract import Ordered, DefinitelyUnordered
2020

@@ -236,7 +236,7 @@ def __or__(self, other):
236236
return NotImplemented
237237
return type(self)(self._dict_type(
238238
(key, max(self[key], other[key]))
239-
for key in OrderedSet(self) | OrderedSet(other))
239+
for key in FrozenOrderedSet(self) | FrozenOrderedSet(other))
240240
)
241241

242242
def __and__(self, other):
@@ -256,7 +256,7 @@ def __and__(self, other):
256256
return NotImplemented
257257
return type(self)(self._dict_type(
258258
(key, min(self[key], other[key]))
259-
for key in OrderedSet(self) & OrderedSet(other))
259+
for key in FrozenOrderedSet(self) & FrozenOrderedSet(other))
260260
)
261261

262262

@@ -277,7 +277,7 @@ def __add__(self, other):
277277
return NotImplemented
278278
return type(self)(self._dict_type(
279279
(key, self[key] + other[key])
280-
for key in OrderedSet(self) | OrderedSet(other))
280+
for key in FrozenOrderedSet(self) | FrozenOrderedSet(other))
281281
)
282282

283283
def __sub__(self, other):

source_py3/python_toolbox/nifty_collections/various_ordered_sets.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ def pop(self, last=True):
158158
key = next(reversed(self) if last else iter(self))
159159
self.discard(key)
160160
return key
161+
162+
def get_frozen(self):
163+
'''Get a frozen version of this ordered set.'''
164+
return FrozenOrderedSet(self)
165+
161166

162167

163168
class EmittingOrderedSet(OrderedSet):

source_py3/test_python_toolbox/test_cute_profile/shared.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def call_and_check_if_profiled(f):
2222

2323
segments_found = [(segment in output) for segment in segments]
2424

25-
if not logic_tools.all_equal(segments_found):
25+
if not logic_tools.all_equivalent(segments_found):
2626
raise Exception("Some segments were found, but some weren't; can't "
2727
"know if this was a profiled call or not. Possibly "
2828
"some of our segments are wrong.")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Copyright 2009-2015 Ram Rachum.
22
# This program is distributed under the MIT license.
33

4-
'''Testing package for `python_toolbox.logic_tools`.'''
4+
'''Testing package for `python_toolbox.logic_tools`.'''

source_py3/test_python_toolbox/test_logic_tools/test_all_equal.py

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright 2009-2015 Ram Rachum.
2+
# This program is distributed under the MIT license.
3+
4+
'''Testing module for `logic_tools.all_equal`.'''
5+
6+
import operator
7+
import itertools
8+
9+
from python_toolbox.logic_tools import all_equivalent
10+
11+
12+
def test():
13+
'''Test the basic working of `all_equal`.'''
14+
_check(False)
15+
_check(True)
16+
17+
18+
def _check(exhaustive):
19+
'''Check the basic working of `all_equal` with given `exhaustive` flag.'''
20+
assert all_equivalent([1, 1, 1, 1], exhaustive=exhaustive)
21+
assert all_equivalent([1, 1, 1.0, 1], exhaustive=exhaustive)
22+
assert all_equivalent(((1 + 0j), 1, 1.0, 1), exhaustive=exhaustive)
23+
assert all_equivalent([], exhaustive=exhaustive)
24+
assert all_equivalent(iter([1, 1, 1.0, 1]), exhaustive=exhaustive)
25+
assert all_equivalent({'meow'}, exhaustive=exhaustive)
26+
assert all_equivalent(['frr', 'frr', 'frr', 'frr'], exhaustive=exhaustive)
27+
28+
assert not all_equivalent([1, 1, 2, 1], exhaustive=exhaustive)
29+
assert not all_equivalent([1, 1, 1.001, 1], exhaustive=exhaustive)
30+
assert not all_equivalent(((1 + 0j), 3, 1.0, 1), exhaustive=exhaustive)
31+
assert not all_equivalent(range(7), exhaustive=exhaustive)
32+
assert not all_equivalent(iter([1, 17, 1.0, 1]), exhaustive=exhaustive)
33+
assert not all_equivalent({'meow', 'grr'}, exhaustive=exhaustive)
34+
assert not all_equivalent(['frr', 'frr', {}, 'frr', 'frr'],
35+
exhaustive=exhaustive)
36+
assert not all_equivalent(itertools.count()) # Not using given `exhaustive`
37+
# flag here because `count()` is
38+
# infinite.
39+
40+
41+
def test_exhaustive_true():
42+
'''Test `all_equal` in cases where `exhaustive=True` is relevant.'''
43+
44+
class FunkyFloat(float):
45+
def __eq__(self, other):
46+
return (abs(self - other) <= 2)
47+
48+
funky_floats = [
49+
FunkyFloat(1),
50+
FunkyFloat(2),
51+
FunkyFloat(3),
52+
FunkyFloat(4)
53+
]
54+
55+
assert all_equivalent(funky_floats)
56+
assert not all_equivalent(funky_floats, exhaustive=True)
57+
58+
59+
60+
def test_custom_relations():
61+
assert all_equivalent(range(4), relation=operator.ne) is True
62+
assert all_equivalent(range(4), relation=operator.ge) is False
63+
assert all_equivalent(range(4), relation=operator.le) is True
64+
assert all_equivalent(range(4), relation=operator.le,
65+
exhaustive=True) is True
66+
# (Always comparing small to big, even on exhaustive.)
67+
68+
assert all_equivalent(range(4),
69+
relation=lambda x, y: (x // 10 == y // 10)) is True
70+
assert all_equivalent(range(4),
71+
relation=lambda x, y: (x // 10 == y // 10),
72+
exhaustive=True) is True
73+
assert all_equivalent(range(8, 12),
74+
relation=lambda x, y: (x // 10 == y // 10)) is False
75+
assert all_equivalent(range(8, 12),
76+
relation=lambda x, y: (x // 10 == y // 10),
77+
exhaustive=True) is False
78+

source_py3/test_python_toolbox/test_math_tools/test_cute_floor_div_and_divmod.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def cute_equal(*items):
1818
# For testing purposes, we need `nan == nan`, so we use `cute_equal`.
1919
if all(isinstance(item, numbers.Number) for item in items):
2020
if all(map(math.isnan, items)): return True
21-
else: return logic_tools.all_equal(items)
21+
else: return logic_tools.all_equivalent(items)
2222
else:
2323
assert all(isinstance(item, tuple) for item in items)
2424
return all(cute_equal(*sub_items) for sub_items in zip(*items))
@@ -49,12 +49,12 @@ def test_illegal_cases():
4949
divmod(*illegal_case)
5050
with cute_testing.RaiseAssertor() as raise_assertor_2:
5151
cute_floor_div(*illegal_case)
52-
assert logic_tools.all_equal((
52+
assert logic_tools.all_equivalent((
5353
type(raise_assertor_0.exception),
5454
type(raise_assertor_1.exception),
5555
type(raise_assertor_2.exception),
5656
))
57-
assert logic_tools.all_equal((
57+
assert logic_tools.all_equivalent((
5858
raise_assertor_0.exception.args,
5959
raise_assertor_1.exception.args,
6060
raise_assertor_2.exception.args,

0 commit comments

Comments
 (0)