package sizzle.aggregators; import java.io.IOException; import sizzle.io.EmitKey; /** * A Sizzle aggregator to calculate the top or bottom <i>n</i> values in a * dataset by weight. * * @author anthonyu * */ abstract class MinOrMaxAggregator extends Aggregator { protected final WeightedString[] list; private final int last; /** * Construct a MinOrMaxAggregator. * * @param n * A long representing the number of values to return */ public MinOrMaxAggregator(final long n) { super(n); // an array of weighted string of length n this.list = new WeightedString[(int) this.getArg()]; // the index of the last entry in the list this.last = (int) (this.getArg() - 1); } /** {@inheritDoc} */ @Override public void start(final EmitKey key) { super.start(key); } /** {@inheritDoc} */ @Override public void aggregate(final String data, final String metadata) { double weight; if (metadata == null) // by default, minimum weight weight = Double.MIN_VALUE; else weight = Double.parseDouble(metadata); final WeightedString s = new WeightedString(data, weight); if (this.compare(s, this.list[this.last]) > 0) { // find this new item's position within the list for (int i = 0; i < this.getArg(); i++) if (this.compare(s, this.list[i]) > 0) { // we found it. move all subsequent items down one spot for (int j = (int) (this.getArg() - 2); j >= i; j--) this.list[j + 1] = this.list[j]; // insert the item where it belongs this.list[i] = s; break; } } } /** * Compare two weighted strings. * * @param a * A {@link WeightedString} containing a {@link String} and its * weight. * * @param b * A {@link WeightedString} containing a {@link String} and its * weight. * * @return An int representing the comparison between the two strings. */ abstract protected int compare(WeightedString s, WeightedString weightedString); /** {@inheritDoc} */ @Override public void finish() throws IOException, InterruptedException { for (int i = 0; i < this.getArg(); i++) if (this.isCombining()) this.collect(this.list[i].getString(), Double.toString(this.list[i].getWeight())); else this.collect(this.list[i].toString()); } /** {@inheritDoc} */ @Override public boolean isAssociative() { return true; } /** {@inheritDoc} */ @Override public boolean isCommutative() { return true; } } /** * A tuple containing a {@link String} and its weight. * * @author anthonyu * */ class WeightedString { private final String string; private final double weight; /** * Construct a WeightedString. * * @param string * A {@link String} containing the string part of the tuple * * @param weight * A double representing the weight part of the tuple */ public WeightedString(final String string, final double weight) { this.string = string; this.weight = weight; } /** * Get the string part of the tuple. * * @return A {@link String} containing the string part of the tuple */ public String getString() { return this.string; } /** * Get the weight part of the tuple. * * @return A double containing the weight part of the tuple */ public double getWeight() { return this.weight; } /** {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (this.string == null ? 0 : this.string.hashCode()); final long temp = Double.doubleToLongBits(this.weight); result = prime * result + (int) (temp ^ temp >>> 32); return result; } /** {@inheritDoc} */ @Override public boolean equals(final Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; final WeightedString other = (WeightedString) obj; if (this.string == null) { if (other.string != null) return false; } else if (!this.string.equals(other.string)) return false; if (Double.doubleToLongBits(this.weight) != Double.doubleToLongBits(other.weight)) return false; return true; } /** {@inheritDoc} */ @Override public String toString() { return this.string + " weight " + this.weight; } }