package de.lmu.ifi.dbs.elki.math.histograms; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import java.util.ArrayList; import java.util.Iterator; import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair; import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleObjPair; import de.lmu.ifi.dbs.elki.utilities.pairs.IntIntPair; /** * Class to manage a simple Histogram. * * Note: the iterator returns pairs containing the coordinate and the bin value! * * @author Erich Schubert * * @apiviz.composedOf de.lmu.ifi.dbs.elki.math.histograms.ReplacingHistogram.Adapter * * @param Histogram data type. */ public class ReplacingHistogram implements Iterable> { /** * Interface to plug in a data type T. * * @author Erich Schubert * * @param Data type */ public static abstract class Adapter { /** * Construct a new T when needed. * * @return new T */ public abstract T make(); } /** * Array shift to account for negative indices. */ protected int offset = 0; /** * Size of array storage. */ protected int size; /** * Array 'base', i.e. the point of 0.0. Usually the minimum. */ protected double base; /** * To avoid introducing an extra bucket for the maximum value. */ protected double max; /** * Width of a bin. */ protected double binsize; /** * Data storage */ protected ArrayList data; /** * Constructor for new elements */ private Adapter maker; /** * Histogram constructor * * @param bins Number of bins to use. * @param min Minimum Value * @param max Maximum Value * @param maker Constructor for new elements. */ public ReplacingHistogram(int bins, double min, double max, Adapter maker) { this.base = min; this.max = max; this.binsize = (max - min) / bins; this.size = bins; this.data = new ArrayList(bins); this.maker = maker; for(int i = 0; i < bins; i++) { this.data.add(maker.make()); } } /** * Histogram constructor without 'Constructor' to generate new elements. Empty * bins will be initialized with 'null'. * * @param bins Number of bins * @param min Minimum value * @param max Maximum value. */ public ReplacingHistogram(int bins, double min, double max) { this(bins, min, max, null); } /** * Get the data at a given Coordinate. * * @param coord Coordinate. * @return data element there (which may be a new empty bin or null) */ public T get(double coord) { int bin = getBinNr(coord); // compare with allocated area if(bin < 0) { T n = maker.make(); return n; } if(bin >= size) { T n = maker.make(); return n; } return data.get(bin); } /** * Put data at a given coordinate. Note: this replaces the contents, it * doesn't "add" or "count". * * @param coord Coordinate * @param d New Data */ public void replace(double coord, T d) { int bin = getBinNr(coord); putBin(bin, d); } /** * Compute the bin number. Has a special case for rounding max down to the * last bin. * * @param coord Coordinate * @return bin number */ protected int getBinNr(double coord) { if(Double.isInfinite(coord) || Double.isNaN(coord)) { throw new UnsupportedOperationException("Encountered non-finite value in Histogram: " + coord); } if(coord == max) { // System.err.println("Triggered special case: "+ (Math.floor((coord - // base) / binsize) + offset) + " vs. " + (size - 1)); return size - 1; } return (int) Math.floor((coord - base) / binsize) + offset; } /** * Internal put function to handle the special cases of histogram resizing. * * @param bin bin number * @param d data to put */ private void putBin(int bin, T d) { if(bin < 0) { // make sure to have enough space data.ensureCapacity(size - bin); // insert new data in front. data.add(0, d); // fill the gap. Note that bin < 0. for(int i = bin + 1; i < 0; i++) { data.add(1, maker.make()); } // We still have bin < 0, thus (size - bin) > size! assert (data.size() == size - bin); offset = offset - bin; size = size - bin; // drop max value when resizing max = Double.MAX_VALUE; } else if(bin >= size) { this.data.ensureCapacity(bin + 1); while(data.size() < bin) { data.add(maker.make()); } // add the new data. data.add(d); assert (data.size() == bin + 1); size = bin + 1; // drop max value when resizing max = Double.MAX_VALUE; } else { this.data.set(bin, d); } } /** * Get the number of bins actually in use. * * @return number of bins */ public int getNumBins() { return size; } /** * Get the size (width) of a bin. * * @return bin size */ public double getBinsize() { return binsize; } /** * Mean of bin * * @param bin Bin number * @return Mean */ public double getBinMean(int bin) { return base + (bin + 0.5 - offset) * binsize; } /** * Minimum of bin * * @param bin Bin number * @return Lower bound */ public double getBinMin(int bin) { return base + (bin - offset) * binsize; } /** * Maximum of bin * * @param bin Bin number * @return Upper bound */ public double getBinMax(int bin) { return base + (bin + 1 - offset) * binsize; } /** * Get minimum (covered by bins, not data!) * * @return minimum */ public double getCoverMinimum() { return base - offset * binsize; } /** * Get maximum (covered by bins, not data!) * * @return maximum */ public double getCoverMaximum() { return base + (size - offset) * binsize; } /** * Get the raw data. Note that this does NOT include the coordinates. * * @return raw data array. */ public ArrayList getData() { return data; } /** * Make a new bin. * * @return new bin. */ protected T make() { return maker.make(); } /** * Iterator class to iterate over all bins. * * @author Erich Schubert * * @apiviz.exclude */ protected class Iter implements Iterator> { /** * Current bin number */ int bin = 0; @Override public boolean hasNext() { return bin < size; } @Override public DoubleObjPair next() { DoubleObjPair pair = new DoubleObjPair(base + (bin + 0.5 - offset) * binsize, data.get(bin)); bin++; return pair; } @Override public void remove() { throw new UnsupportedOperationException("Histogram iterators cannot be modified."); } } /** * Iterator class to iterate over all bins. * * @author Erich Schubert * * @apiviz.exclude */ protected class RIter implements Iterator> { /** * Current bin number */ int bin = size - 1; @Override public boolean hasNext() { return bin >= 0; } @Override public DoubleObjPair next() { DoubleObjPair< T> pair = new DoubleObjPair(base + (bin + 0.5 - offset) * binsize, data.get(bin)); bin--; return pair; } @Override public void remove() { throw new UnsupportedOperationException("Histogram iterators cannot be modified."); } } /** * Get an iterator over all histogram bins. */ @Override public Iterator> iterator() { return new Iter(); } /** * Get an iterator over all histogram bins. */ // TODO: is there some interface to implement. public Iterator> reverseIterator() { return new RIter(); } /** * Convenience constructor for Integer-based Histograms. Uses a constructor to * initialize bins with Integer(0) * * @param bins Number of bins * @param min Minimum coordinate * @param max Maximum coordinate * @return New histogram for Integers. */ public static ReplacingHistogram IntHistogram(int bins, double min, double max) { return new ReplacingHistogram(bins, min, max, new Adapter() { @Override public Integer make() { return new Integer(0); } }); } /** * Convenience constructor for Double-based Histograms. Uses a constructor to * initialize bins with Double(0) * * @param bins Number of bins * @param min Minimum coordinate * @param max Maximum coordinate * @return New histogram for Doubles. */ public static ReplacingHistogram DoubleHistogram(int bins, double min, double max) { return new ReplacingHistogram(bins, min, max, new Adapter() { @Override public Double make() { return new Double(0.0); } }); } /** * Convenience constructor for Histograms with pairs of Integers Uses a * constructor to initialize bins with Pair(Integer(0),Integer(0)) * * @param bins Number of bins * @param min Minimum coordinate * @param max Maximum coordinate * @return New histogram for Integer pairs. */ public static ReplacingHistogram IntIntHistogram(int bins, double min, double max) { return new ReplacingHistogram(bins, min, max, new Adapter() { @Override public IntIntPair make() { return new IntIntPair(0, 0); } }); } /** * Convenience constructor for Histograms with pairs of Doubles Uses a * constructor to initialize bins with Pair(Double(0),Double(0)) * * @param bins Number of bins * @param min Minimum coordinate * @param max Maximum coordinate * @return New histogram for Double pairs. */ public static ReplacingHistogram DoubleDoubleHistogram(int bins, double min, double max) { return new ReplacingHistogram(bins, min, max, new Adapter() { @Override public DoubleDoublePair make() { return new DoubleDoublePair(0.0, 0.0); } }); } }