2,829 bytes · the Python implementation · view raw
from math import gcd
from typing import List, Sequence
from .stats_mean_median_types import CentralTendency, Fraction
MAX_SAFE = 9007199254740991def _fraction(numerator: int, denominator: int) -> Fraction:
g = gcd(numerator, denominator) or1return Fraction(numerator=numerator // g, denominator=denominator // g)
def mean_median_mode(values: Sequence[int]) -> CentralTendency:
"""Mean, median and mode of a list of integers. Mean and median are exact fractions; their float forms come from a single correctly rounded division, so every language returns the same double. """if isinstance(values, (str, bytes)) ornot isinstance(values, (list, tuple)):
raise TypeError("values must be a list of integers")
if len(values) == 0:
raise ValueError("values must not be empty")
for v in values:
# bool is an int in Python; every other language rejects it, so do too.if isinstance(v, bool) ornot isinstance(v, int):
raise TypeError("values must be integers, received %r" % (v,))
if abs(v) > MAX_SAFE:
raise ValueError("values must be safe integers (magnitude at most 9007199254740991), received %d" % v)
total = sum(values)
if abs(total) > MAX_SAFE:
raise ValueError("sum exceeds the safe integer range")
count = len(values)
ordered = sorted(values)
if count % 2 == 1:
median = Fraction(numerator=ordered[(count - 1) // 2], denominator=1)
else:
pair = ordered[count // 2 - 1] + ordered[count // 2]
if pair % 2 == 0:
median = Fraction(numerator=pair // 2, denominator=1)
else:
if abs(pair) > MAX_SAFE:
raise ValueError("median exceeds the safe integer range")
median = Fraction(numerator=pair, denominator=2)
# Runs of equal values in the sorted copy give frequencies in ascending# value order, so the modes come out sorted without a second sort.
modes: List[int] = []
mode_frequency = 0
i = 0while i < count:
j = i
while j < count and ordered[j] == ordered[i]:
j += 1
run = j - i
if run > mode_frequency:
mode_frequency = run
modes = [ordered[i]]
elif run == mode_frequency:
modes.append(ordered[i])
i = j
# float(a) / float(b) with both exact (|a| <= 2^53) is one correctly# rounded IEEE division, the same double TypeScript and Rust produce.return CentralTendency(
count=count,
sum=total,
mean=_fraction(total, count),
mean_value=float(total) / float(count) + 0.0,
median=median,
median_value=float(median.numerator) / float(median.denominator) + 0.0,
modes=modes,
mode_frequency=mode_frequency,
)