Files

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()