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