/*
* 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 com.addthis.hydra.data.util;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import com.addthis.codec.annotations.FieldConfig;
import com.addthis.codec.codables.SuperCodable;
/**
* maintains a list of the number of keys in each
* value bucket. buckets are configurable as
* powers of N (from the scale setting).
* <p/>
* TODO store # of things in each bucket as well as total of key values in each bucket
*/
public class KeyHistogram implements SuperCodable {
@FieldConfig(codable = true)
private HashMap<Long, Long> map;
@FieldConfig(codable = true)
private int scale = 10;
public KeyHistogram init() {
map = new HashMap<>();
return this;
}
public KeyHistogram setScale(int scale) {
if (scale < 2) {
throw new RuntimeException("scale values must be >= 2");
}
this.scale = scale;
return this;
}
public Map<Long, Long> getHistogram() {
return map;
}
public Map<Long, Long> getSortedHistogram() {
TreeMap<Long, Long> sort = new TreeMap<>();
for (Entry<Long, Long> e : map.entrySet()) {
sort.put(e.getKey(), e.getValue());
}
return sort;
}
public boolean incrementFrom(long from) {
return update(from, from + 1);
}
public boolean incrementTo(long to) {
return update(to - 1, to);
}
/**
* @return returns true if the map was changed by the update
*/
public boolean update(long from, long to) {
if (from == to) {
return false;
}
if (from == 0) {
incTo(getBucket(to));
return true;
}
if (to == 0) {
decFrom(getBucket(from));
return true;
}
long bucketFrom = getBucket(from);
long bucketTo = getBucket(to);
if (bucketFrom != bucketTo) {
incTo(bucketTo);
decFrom(bucketFrom);
return true;
}
return false;
}
private void decFrom(long bucketFrom) {
Long val = Long.valueOf(bucketFrom);
Long dec = map.get(val);
if (dec == null || dec == 0) {
map.put(val, 0L);
} else {
map.put(val, dec - 1);
}
}
private void incTo(long bucketTo) {
Long val = Long.valueOf(bucketTo);
Long inc = map.get(val);
if (inc == null) {
map.put(val, 1L);
} else {
map.put(val, inc + 1);
}
}
private long getBucket(long val) {
long compare = 1;
long last = compare;
while (val >= compare) {
last = compare;
compare *= scale;
}
return last;
}
@Override
public void postDecode() {
if (map == null) {
init();
}
}
@Override
public void preEncode() {
}
}