108 lines
3.0 KiB
Python
108 lines
3.0 KiB
Python
"""Generalized Boyer-Moore majority vote.
|
|
|
|
Finds all elements occurring more than n/k times in a sequence, in O(n*k)
|
|
time and O(k) extra space. Candidate selection alone is not sufficient:
|
|
a final verification pass is mandatory, because candidates can be false
|
|
positives produced by cancellation bookkeeping.
|
|
"""
|
|
from collections import Counter
|
|
from typing import Iterable, List
|
|
|
|
|
|
def majority_element_v2(nums):
|
|
data = list(nums)
|
|
cand = None
|
|
count = 0
|
|
for x in data:
|
|
if count == 0:
|
|
cand = x
|
|
count = 1
|
|
elif x == cand:
|
|
count += 1
|
|
else:
|
|
count -= 1
|
|
if not data:
|
|
return None
|
|
if data.count(cand) > len(data) // 2:
|
|
return cand
|
|
return None
|
|
|
|
|
|
def majority_element(nums):
|
|
"""Return the element occurring more than len(nums)//2 times, or None."""
|
|
return majority_element_v2(nums)
|
|
|
|
|
|
def majority_gt_k(nums, k: int) -> List:
|
|
"""Return all elements occurring more than len(nums)//k times (k>=2)."""
|
|
if k < 2:
|
|
raise ValueError("k must be >= 2")
|
|
data = list(nums)
|
|
n = len(data)
|
|
if n == 0:
|
|
return []
|
|
cand = {}
|
|
for x in data:
|
|
if x in cand:
|
|
cand[x] += 1
|
|
elif len(cand) < k - 1:
|
|
cand[x] = 1
|
|
else:
|
|
to_remove = []
|
|
for key in list(cand):
|
|
cand[key] -= 1
|
|
if cand[key] == 0:
|
|
to_remove.append(key)
|
|
for key in to_remove:
|
|
del cand[key]
|
|
candidates = list(cand.keys())
|
|
threshold = n // k
|
|
counts = Counter(data)
|
|
result = []
|
|
seen = set()
|
|
for c in candidates:
|
|
if counts.get(c, 0) > threshold and c not in seen:
|
|
seen.add(c)
|
|
result.append(c)
|
|
return result
|
|
|
|
|
|
def majority(nums):
|
|
return majority_element_v2(nums)
|
|
|
|
|
|
def majority_third(nums):
|
|
return majority_gt_k(nums, 3)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import random
|
|
rng = random.Random(42)
|
|
for trial in range(2000):
|
|
n = rng.randint(0, 40)
|
|
if n == 0:
|
|
data = []
|
|
else:
|
|
if rng.random() < 0.5:
|
|
maj = rng.randint(0, 5)
|
|
need = n // 2 + 1
|
|
data = [maj] * need + [rng.randint(0, 5) for _ in range(n - need)]
|
|
rng.shuffle(data)
|
|
else:
|
|
data = [rng.randint(0, 5) for _ in range(n)]
|
|
c = Counter(data)
|
|
bf = [x for x, v in c.items() if v > n // 2] if data else []
|
|
bf_majority = bf[0] if bf else None
|
|
got = majority_element_v2(data)
|
|
assert got == bf_majority, (data, got, bf_majority)
|
|
bf3 = [x for x, v in c.items() if v > n // 3]
|
|
got3 = majority_gt_k(data, 3)
|
|
assert sorted(got3) == sorted(bf3), (data, got3, bf3)
|
|
print("ALL RANDOMIZED CHECKS PASSED")
|
|
assert majority_element_v2([]) is None
|
|
assert majority_element_v2([1]) == 1
|
|
assert majority_element_v2([1, 2]) is None
|
|
assert majority_gt_k([], 3) == []
|
|
assert majority_gt_k([1, 1, 2, 2, 3, 3], 3) == []
|
|
print("EDGE CASES PASSED")
|