Files

110 lines
4.4 KiB
Python

"""Cells for majority.py (Q85): the generalized Boyer-Moore vote.
No collected test imported the module, so its `__main__` corpus was the only
thing that ever ran it. Every cell here compares against a Counter, which
shares no code with the module, so the vote's bookkeeping is graded rather
than echoed.
"""
import random
import unittest
from collections import Counter
import majority as M
def brute_gt_k(data, k):
n = len(data)
return sorted(x for x, c in Counter(data).items() if c > n // k)
def brute_majority(data):
if not data:
return None
hits = [x for x, c in Counter(data).items() if c > len(data) // 2]
return hits[0] if hits else None
class TestMajority(unittest.TestCase):
def test_empty_and_small(self):
self.assertIsNone(M.majority_element([]))
self.assertEqual(M.majority_element([1]), 1)
self.assertIsNone(M.majority_element([1, 2]))
self.assertEqual(M.majority_element([1, 1, 2]), 1)
self.assertIsNone(M.majority_element([1, 2, 3]))
def test_odd_and_even_near_misses(self):
# Exactly half is not a majority; one more than half is.
self.assertIsNone(M.majority_element([1, 1, 2, 2]))
self.assertEqual(M.majority_element([1, 1, 1, 2]), 1)
self.assertIsNone(M.majority_element([1, 1, 2, 3]))
def test_aliases_agree(self):
data = [4, 4, 4, 1, 2]
self.assertEqual(M.majority(data), M.majority_element_v2(data))
self.assertEqual(M.majority(data), 4)
def test_counter_resistant_pattern(self):
# The order that defeats a naive "cancel adjacent" reading: no prefix
# has more candidates than cancellations until the very end.
data = [2, 1, 2, 1, 2]
self.assertEqual(M.majority_element(data), 2)
data = [1, 2, 1, 2, 1, 2, 2]
self.assertEqual(M.majority_element(data), 2)
def test_random_majority_against_counter(self):
rng = random.Random(42)
for _ in range(1500):
n = rng.randint(0, 40)
if n and rng.random() < 0.5:
maj = rng.randint(0, 5)
data = [maj] * (n // 2 + 1) + [rng.randint(0, 5) for _ in range(n - n // 2 - 1)]
rng.shuffle(data)
else:
data = [rng.randint(0, 5) for _ in range(n)]
self.assertEqual(M.majority_element(data), brute_majority(data), data)
def test_gt_k_is_sound_and_complete(self):
# Returned values are exactly the ones above n//k -- no false positive
# (the docstring's reason for the verification pass) and no omission.
rng = random.Random(1234)
for _ in range(800):
n = rng.randint(0, 30)
data = [rng.randint(0, 6) for _ in range(n)]
for k in (2, 3, 4, 5):
got = M.majority_gt_k(data, k)
self.assertEqual(sorted(got), brute_gt_k(data, k), (data, k))
self.assertEqual(len(got), len(set(got)), "duplicate in the result")
self.assertLessEqual(len(got), k - 1)
def test_gt_k_structure_cases(self):
self.assertEqual(M.majority_gt_k([], 3), [])
self.assertEqual(M.majority_gt_k([1, 1, 2, 2, 3, 3], 3), [])
self.assertEqual(sorted(M.majority_gt_k([1, 1, 1, 2, 2, 3], 3)), [1])
# "More than n//k" is strict: at k=2, exactly half of four is not a
# majority, so nothing is returned. (Written the other way first and
# the cell failed -- the module was right, the expectation was not.)
self.assertEqual(M.majority_gt_k([1, 1, 2, 2], 2), [])
self.assertEqual(sorted(M.majority_gt_k([1, 1, 1, 2], 2)), [1])
def test_third_is_k_three(self):
self.assertEqual(M.majority_third([1, 1, 2]), [1])
self.assertEqual(M.majority_third([1, 1, 2, 2, 3, 3]), [])
self.assertEqual(M.majority_third([1, 1, 1, 2, 3]), [1])
def test_k_below_two_is_refused(self):
for k in (1, 0, -3):
with self.assertRaises(ValueError, msg="k=%r" % k):
M.majority_gt_k([1, 2], k)
def test_one_shot_input_is_materialised(self):
# `data = list(nums)` is what makes a generator safe; without it the
# second pass over `nums` would see nothing.
gen = (x for x in [1, 1, 2])
self.assertEqual(M.majority_element(gen), 1)
gen = (x for x in [1, 1, 2, 2, 3, 3])
self.assertEqual(M.majority_gt_k(gen, 3), [])
if __name__ == "__main__":
unittest.main()