= Solution
For the <product of finite probability distributions>, enumerate the <Cartesian product> of the two supports and multiply the two <probability masses>.
``
def product[T,U](d: FiniteDist[T], d2: FiniteDist[U]): FiniteDist[(T,U)] =
new FiniteDist[(T,U)] {
private val dom = for (x <- d.getDom; y <- d2.getDom) yield (x,y)
def getDom: List[(T,U)] = dom
def getProb(p: (T,U)): Double = d.getProb(p._1) * d2.getProb(p._2)
}
``
Each pair appears once because both original supports have no duplicates. Exact <probabilities> are positive on that support, and normalization follows by finite distributivity:
$$
\sum_{x,y}p_1(x)p_2(y)=\left(\sum_xp_1(x)\right)\left(\sum_yp_2(y)\right)=1.
$$
For the <pushforward of a finite probability distribution>, sum all masses in each <fiber of a function>; simply overwriting repeated image values would lose <probability>.
``
def image[T,U](d: FiniteDist[T], f: T => U): FiniteDist[U] =
new FiniteDist[U] {
private val weighted = d.getDom.map(x => (f(x), d.getProb(x)))
private val dom = weighted.map(_._1).distinct
def getDom: List[U] = dom
def getProb(y: U): Double =
weighted.foldLeft(0.0) { case (total, (z,p)) =>
if (z == y) total+p else total
}
}
``
Assume the given <finite probability distributions> are fixed and `f` is a pure total <function> on the source support. Distinct image values form exactly the positive-mass support. The <fibers> partition the original support, proving
$$
\boxed{p_{\mathrm{product}}(x,y)=p_1(x)p_2(y),\qquad p_{\mathrm{image}}(z)=\sum_{x:f(x)=z}p_1(x),\qquad\sum_zp_{\mathrm{image}}(z)=1.}
$$
For example, sending a <discrete uniform distribution> on $\{1,2,3,4\}$ to parity produces masses $1/2$ and $1/2$, not four distinct outcomes. Exact algebra establishes the <finite probability distribution> laws; `Double` computes approximations, with the usual possibility of rounding and <floating-point underflow> for very small products.
Back to article page