113 lines
4.3 KiB
Python
113 lines
4.3 KiB
Python
"""Contract cells for majority(): three outcomes, three channels (Q102).
|
|
|
|
The old suite pinned six int cases and asserted `is None` for both the
|
|
no-majority and empty lists -- the two cases the module collapsed. These
|
|
cells pin the distinction itself: an element is returned (even None or NaN),
|
|
an empty sequence raises EmptySequence, a sequence without a majority raises
|
|
NoMajority, and the two exceptions are different types.
|
|
"""
|
|
import math
|
|
import random
|
|
import unittest
|
|
from collections import Counter
|
|
|
|
from majority_element import EmptySequence, NoMajority, majority
|
|
|
|
|
|
class NeverEqual:
|
|
"""An object whose `==` is never true, not even against itself."""
|
|
|
|
def __eq__(self, other):
|
|
return False
|
|
|
|
def __ne__(self, other):
|
|
return True
|
|
|
|
def __hash__(self):
|
|
raise TypeError("NeverEqual is not hashable")
|
|
|
|
|
|
class TestMajorityContract(unittest.TestCase):
|
|
def test_int_majorities_still_return_the_element(self):
|
|
self.assertEqual(majority([1, 1, 2]), 1)
|
|
self.assertEqual(majority([2, 2, 1, 1, 1]), 1)
|
|
self.assertEqual(majority([5]), 5)
|
|
self.assertEqual(majority([1, 1, 1, 2, 2, 2, 2]), 2)
|
|
|
|
def test_empty_sequence_raises_empty_sequence(self):
|
|
with self.assertRaises(EmptySequence):
|
|
majority([])
|
|
# Empty is its own outcome, not the no-majority outcome.
|
|
self.assertFalse(issubclass(EmptySequence, NoMajority))
|
|
|
|
def test_no_majority_raises_no_majority(self):
|
|
for case in ([1, 2, 3], [1, 2, 1, 2], [0, 1] * 1000, [1, 1, 2, 2, 3]):
|
|
with self.assertRaises(NoMajority):
|
|
majority(case)
|
|
|
|
def test_none_majority_is_returned_not_raised(self):
|
|
self.assertIsNone(majority([None, None, 1]))
|
|
self.assertIsNone(majority([None]))
|
|
|
|
def test_empty_and_no_majority_are_distinct_types(self):
|
|
self.assertIsNot(EmptySequence, NoMajority)
|
|
self.assertTrue(issubclass(EmptySequence, ValueError))
|
|
self.assertTrue(issubclass(NoMajority, ValueError))
|
|
with self.assertRaises(ValueError) as empty_exc:
|
|
majority([])
|
|
with self.assertRaises(ValueError) as no_majority_exc:
|
|
majority([1, 2, 3])
|
|
self.assertIs(type(empty_exc.exception), EmptySequence)
|
|
self.assertIs(type(no_majority_exc.exception), NoMajority)
|
|
|
|
def test_shared_nan_object_is_a_majority(self):
|
|
nan = float("nan")
|
|
self.assertIs(majority([nan, nan, 1.0]), nan)
|
|
|
|
def test_distinct_nan_objects_are_a_majority(self):
|
|
# IEEE says nan != nan; this module's counting rule says NaN == NaN,
|
|
# so a list that is two-thirds NaN has a majority, and it is a nan.
|
|
a, b = float("nan"), float("nan")
|
|
result = majority([a, b, 1.0])
|
|
self.assertIsInstance(result, float)
|
|
self.assertTrue(math.isnan(result))
|
|
|
|
def test_nonreflexive_element_counts_by_identity(self):
|
|
obj = NeverEqual()
|
|
self.assertIs(majority([obj, obj, 1]), obj)
|
|
# Two distinct never-equal objects are two different values, so this
|
|
# is not a majority -- the rule must not conflate them.
|
|
with self.assertRaises(NoMajority):
|
|
majority([NeverEqual(), NeverEqual(), 1])
|
|
|
|
def test_generator_is_accepted_and_consumed_once(self):
|
|
self.assertEqual(majority(iter([1, 1, 2])), 1)
|
|
with self.assertRaises(EmptySequence):
|
|
majority(iter(()))
|
|
|
|
def test_differential_against_counter(self):
|
|
rng = random.Random(102)
|
|
for _ in range(2000):
|
|
arr = [rng.choice([0, 1, 2, 3]) for _ in range(rng.randrange(0, 10))]
|
|
if not arr:
|
|
with self.assertRaises(EmptySequence):
|
|
majority(arr)
|
|
continue
|
|
counts = Counter(arr)
|
|
winner = [v for v, c in counts.items() if c * 2 > len(arr)]
|
|
if winner:
|
|
self.assertEqual(majority(arr), winner[0], arr)
|
|
else:
|
|
with self.assertRaises(NoMajority, msg=repr(arr)):
|
|
majority(arr)
|
|
|
|
def test_messages_name_the_threshold(self):
|
|
with self.assertRaisesRegex(NoMajority, "more than 1 times in a 3-element"):
|
|
majority([1, 2, 3])
|
|
with self.assertRaisesRegex(EmptySequence, "empty sequence"):
|
|
majority([])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|