Article 494RG Fixing Random, part 5

Fixing Random, part 5

by
ericlippert
from Fabulous adventures in coding on (#494RG)

[Code is here.]

Last time on FAIC I implemented our first discrete distribution, the "choose an integer between min and max" distribution. I thought I might knock out a couple more of the easy discrete distributions. The easiest discrete distribution of all is of course the trivial distribution:

public sealed class Singleton<T> : IDiscreteDistribution<T>
{
private readonly T t;
public static Singleton<T> Distribution(T t) =>
new Singleton<T>(t);
private Singleton(T t) => this.t = t;
public T Sample() => t;
public IEnumerable<T> Support()
{
yield return t;
}
public int Weight(T t) =>
EqualityComparer<T>.Default
.Equals(this.t, t) ? 1 : 0;
public override string ToString() =>
$"Singleton[{t}]";
}

That is, the probability distribution where 100% of the time it returns the same value. You'll also sometimes see distributions like this called the "Dirac delta" or the "Kronecker delta", but we're not doing quantum physics here, so I'm going to just call this the singleton distribution and be done with it.

Aside: I wish the Enumerable class had the corresponding method: take an item, return the sequence containing only that item. As we just saw, it's easy to implement; because it is not in the framework, I've implemented it literally dozens of times! It is weird to me that the "extract the value of a singleton sequence" is a member of Enumerable, but the inverse operation is not.

Aside: This is the first distribution we've seen that does not necessarily have some sort of number as its output. You might be thinking that maybe our distribution type should never have been generic in the first place, if the only distributions I'm going to come up with are either numeric-valued or trivial. But worry not! In the next episodes we'll see how we can naturally use more complex types in probability distributions.

Let's look at another integer-valued distribution. The Bernoulli distribution considers the question "what if we flipped a possibly-unfair coin, and scored 1 for a head and 0 for a tail?" The parameter to the distribution is traditionally the probability of heads in our coin, between 0.0 and 1.0. However, as I noted last time, at least for now I'm going to try to avoid double weights. Instead, we'll think of this distribution as "odds". Instead of saying "0.7 chance of getting a 1", we'll say "we get three zeros for every seven ones":

public sealed class Bernoulli :
IDiscreteDistribution<int>
{
public static IDiscreteDistribution<int> Distribution(
int zero, int one)
{
if (zero < 0 || one < 0 || zero == 0 && one == 0)
throw new ArgumentException();
if (zero == 0) return Singleton<int>.Distribution(1);
if (one == 0) return Singleton<int>.Distribution(0);
return new Bernoulli(zero, one);
}
public int Zero { get; }
public int One { get; }
private Bernoulli(int zero, int one)
{
this.Zero = zero;
this.One = one;
}
public int Sample() =>
(SCU.Distribution.Sample() <=
((double)Zero) / (Zero + One)) ? 0 : 1;
public IEnumerable<int> Support() =>
Enumerable.Range(0, 2);
public int Weight(int x) => x == 0 ? Zero : x == 1 ? One : 0;
public override string ToString() =>
$"Bernoulli[{this.Zero}, {this.One}]";
}

And sure enough, if we graph it out:

Bernoulli.Distribution(1, 3).Histogram()

We get what you'd expect: three times as many heads as tails:

0|*************1|****************************************

Next time on FAIC: We'll stop making new implementations of classic probability distributions and look at how to extend the ones we've got.

External Content
Source RSS or Atom Feed
Feed Location http://ericlippert.com/feed
Feed Title Fabulous adventures in coding
Feed Link https://ericlippert.com/
Reply 0 comments