/*******************************************************************************
* Copyright 2014 Felipe Takiyama
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package br.usp.poli.takiyama.prv;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import br.usp.poli.takiyama.utils.Lists;
import br.usp.poli.takiyama.utils.MathUtils.Multinomial;
/**
* This class represents the elements of the range of a counting formula.
* Histograms are tuples composed by buckets, which in turn store the
* count of elements from the range of the counted parameterized random
* variable.
*
* @author Felipe Takiyama
*
* @param <T> The type of element in the range of the counted
* parameterized random variable. For now, it is String.
*/
public final class Histogram<T extends RangeElement> implements RangeElement {
private LinkedHashMap<T, Integer> distribution;
/* ************************************************************************
* Constructors
* ************************************************************************/
/**
* Creates an empty histogram.
* @param prvRange A list with the elements of the range of some
* parameterized random variable
*/
Histogram(List<T> prvRange) {
distribution = new LinkedHashMap<T, Integer>(2 * prvRange.size());
for (int i = 0; i < prvRange.size(); i++) {
distribution.put(prvRange.get(i), Integer.valueOf(0));
}
}
/**
* Creates a copy of the specified histogram.
* @param histogram The histogram to copy
*/
Histogram(Histogram<? extends T> histogram) {
this.distribution =
new LinkedHashMap<T, Integer>(histogram.distribution);
}
/* ************************************************************************
* Getters
* ************************************************************************/
/**
* Returns the count of the specified bucket
*
* @param rangeValue The key to the bucket
* @return The count of the specified bucket
*/
int getCount(T rangeValue) {
return distribution.get(rangeValue);
}
/**
* Returns the number of buckets in this histogram, which equals to
* the size of range(f), where f is the parameterized random variable
* being counted.
*
* @return The number of buckets in this histogram
*/
int size() {
return distribution.size();
}
/**
* Returns <code>true</code> if this histogram contains the specified
* bucket.
*
* @param bucket The bucket to search for
* @return <code>true</code> if this histogram contains the specified
* bucket, <code>false</code> otherwise
* @see #combine(RangeElement)
*/
private boolean contains(RangeElement bucket) {
return distribution.containsKey(bucket);
}
/**
* Returns true if this histogram contains a bucket with the
* specified count.
*
* @param count A count to check
* @return true If this histogram contains a bucket with the
* specified count, false otherwise.
*/
public boolean containsValue(int count) {
return distribution.containsValue(Integer.valueOf(count));
}
/* ************************************************************************
* Setters
* ************************************************************************/
/**
* Adds the specified amount to the count of the specified range
* element.
*
* @param rangeValue The key to the bucket
* @param amount The amount to sum to the bucket
*/
void addCount(T rangeValue, int amount) {
distribution.put(rangeValue, distribution.get(rangeValue) + amount);
}
/**
* Set the specified amount as the count for the specified bucket.
*
* @param rangeValue The key to the bucket
* @param amount The amount to set into the bucket
*/
void setCount(T rangeValue, int amount) {
distribution.put(rangeValue, amount);
}
/**
* Converts the values contained in each bucket to a {@link Multinomial}
* object.
*
* @return This histogram converted to a Multinomial.
*/
Multinomial toMultinomial() {
List<Integer> values = new ArrayList<Integer>(distribution.values());
return Multinomial.getInstance(values);
}
/**
* Returns this histogram with the count of the specified bucket
* incremented by 1.
*
*/
public RangeElement combine(RangeElement e) {
RangeElement result = null;
if (contains(e)) {
Histogram<RangeElement> copy = new Histogram<RangeElement>(this);
copy.addCount(e, 1);
result = copy;
} else {
throw new IllegalArgumentException();
}
return result;
}
/**
* Returns the result of applying the specified operator to the elements of
* the expanded histogram corresponding to this object.
* <p>
* The expanded histogram is a list obtained by adding to it each range
* element
* in this histogram the number of times specified by its bucket. For
* instance, suppose that h = (#.false = 2, #.true = 3) is a histogram.
* Then the expanded histogram list is {false, false, true, true, true}.
* </p>
*/
@Override
public RangeElement apply(Operator<? extends RangeElement> op) {
List<T> expandedHistogram = new ArrayList<T>();
for (T key : distribution.keySet()) {
int count = getCount(key);
expandedHistogram.addAll(Lists.listOf(key, count));
}
return apply(op, expandedHistogram);
}
/**
* Returns the result of applying the specified operator to the specified
* set of elements. This is a helper method.
*
* @param <E> The type of element to which the operator applies
* @param op The operator to apply
* @param elements The set of elements where the operator will be applied
* @return The result of applying the specified operator to the specified
* set of elements
*/
private <E extends RangeElement> E apply(Operator<E> op,
Collection<T> elements) {
Set<E> elems = new HashSet<E>((int) (elements.size() / 0.75));
for (RangeElement e : elements) {
elems.add(op.getTypeArgument().cast(e));
}
return op.applyOn(elems);
}
/* ************************************************************************
* hashCode, equals and toString
* ************************************************************************/
@Override
public String toString() {
StringBuilder histogram = new StringBuilder();
histogram.append("( ");
for (T key : distribution.keySet()) {
histogram.append("#.").append(key).append("=")
.append(distribution.get(key)).append(", ");
}
histogram.deleteCharAt(histogram.lastIndexOf(","));
histogram.append(")");
return histogram.toString();
}
@Override
public boolean equals(Object other) {
if (this == other)
return true;
if (!(other instanceof Histogram<?>))
return false;
Histogram<?> targetObject = (Histogram<?>) other;
return (this.distribution == null)
? (targetObject.distribution == null)
: this.distribution.equals(targetObject.distribution);
}
@Override
public int hashCode() {
int result = 48 + this.distribution.hashCode(); // 48 = 17 + 31
return result;
}
}