from typing import Tuple
import logging


def _mean_var_without(
    e: float, mean_with_e: float, var_with_e: float, n_with_e: int
) -> Tuple[float, float]:
    """Given a sample variance and a corresponding sample mean, both
    including element `e`, calculate the mean and variance of the sample
    without element `e`.

    Returns
    -------
    (mean, var) : (float, float)
        The mean an variance which remain when the effect of `e` is
        removed from `var_with_e` and `mean_with_e`.

    Notes
    -----
    This implementation is ased on
    https://stackoverflow.com/a/30876815/3389669 and therefore does not
    need to reiterate over the whole sample again.
    """
    if n_with_e < 2:
        raise ValueError("Mean and variance undefined for (to be) empty sequence.")
    mean_without_e = mean_with_e - (e - mean_with_e) / (n_with_e - 1)
    if n_with_e == 2:
        return (mean_without_e, 0.0)
    # The first summand is multiplied by `n_with_e`, because the
    # aggregate in the cited formula is just the sum of squared
    # differences, not their average.
    var_without_e = (
        var_with_e * n_with_e - (e - mean_without_e) * (e - mean_with_e)
    ) / (n_with_e - 1)
    if var_without_e < 0.0:
        logging.warning(
            "Numerical instability detected. Variance is negative. "
            "e={}, mean_with_e={}, var_with_e={}, n_with_e={}, "
            "mean={}, var={}. Setting variance to 0.0.".format(
                e, mean_with_e, var_with_e, n_with_e, mean_without_e, var_without_e
            )
        )
        return (mean_without_e, 0.0)
    else:
        return (mean_without_e, var_without_e)