# -*- coding: utf-8 -*-
"""
Classes for representing sample spaces.

Consider the following distribution:

    d = dit.Distribution(['00', '01'], [.5, .5],
                         sample_space=['00', '01', '10', '11'])

When we marginalize, we only iterate over the elements explicitly stored in
the pmf. Before this module was implemented, whenever the sample space of a
distribution was not explicitly specified, we took the outcomes to be the
sample space, in this case ['00', '01']. This had undesirable side effects.
For example, d.marginal([0]) would only have a sample space of ['0']. What we
really would like is to be able to marginalize the sample space as well, so
that we get a marginal sample space of ['0', '1'].

However, the only way to achieve that is the iterate over the entire sample
space.  But while coalescing, we chose to iterate through the explicitly stored
elements for efficiency reasons---as only those elements could affect the
subsequent pmf. Since sample spaces can get quite large, especially if your
sample space is a Cartesian product, there would be a fairly large penalty
to doing this.

We usually like to work with Cartesian product sample spaces, and if the
product is abstractly represented, then we can marginalize sample spaces very
quickly just by combining the alphabets for each random variable. Since this
is our typical use case, we optimize for it.

With this module implemented, we now assume the user wants a Cartesian product
sample space and build it whenever one is not explicitly passed in. This gives
us desirable behavior when marginalizing, etc. If the user does pass in a
custom sample space, then we use it, but this requires that we iterate through
the whole sample space during marginalization. So this particular use case will
experience the penalty discussed above.

"""