/**
* Copyright [2011] [Datasalt Systems S.L.]
*
* 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.datasalt.utils.commons.count;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Class for counting, creating list of distinct elements and count distinct,
* aggregating by several dimensions. <br>
* For example: <code>
* Counter c = new Counter(true);
* c.in("Animals").in("Dog").in("Mastin").count("toby");
* c.in("Animals").in("Dog").in("BullDog").count("toby");
* c.in("Animals").in("Dog").in("BullDog").count("brian");
*
* System.out.println(ObjectUtils.toIndentedJSON(c.getCounts()));
* </code>
*
* would print:<br/>
*
* <code>
* {
* "nextLevels" : {
* "Animals" : {
* "nextLevels" : {
* "Dog" : {
* "nextLevels" : {
* "Mastin" : {
* "nextLevels" : null,
* "distinctList" : [ "toby" ],
* "count" : 1
* },
* "BullDog" : {
* "nextLevels" : null,
* "distinctList" : [ "brian", "toby" ],
* "count" : 2
* }
* },
* "distinctList" : [ "brian", "toby" ],
* "count" : 3
* }
* },
* "distinctList" : [ "brian", "toby" ],
* "count" : 3
* }
* },
* "distinctList" : [ "brian", "toby" ],
* "count" : 3
* }
* </code>
*
* It supports whatever object as level, for example enums. <br/>
*
* This class can compose distinct lists to count distinct elements. This class
* is not thread safe.
*
* @author ivan
*/
public class Counter {
public static class CounterException extends Exception {
private static final long serialVersionUID = 1L;
public CounterException(String message) {
super(message);
}
}
private boolean countDistinct = true;
/**
* This method creates a counter that besides just counting numerically , it
* keeps track of the distinct elements counted.
*
*
*/
public static Counter createWithDistinctElements() {
return new Counter(true);
}
/**
* This creates a counter that doesn't store elements, just numeric counters,
* hence the methods to retrieve elements like {@link Count#getDistinctListAsStringList()} and {@link Count#getDistinctList()} throw an exception.
*/
public static Counter numericCounter() {
return new Counter(false);
}
public static class Count {
private long count = 0;
private Set<Comparable<?>> distinctList = null;
private Map<Object, Count> nextLevels = null;
private boolean countDistinct;
protected Count(boolean countDistinct) {
distinctList = Sets.newTreeSet();
this.countDistinct = countDistinct;
}
/**
* TODO Important! document this much better or refactor. This count is like a SELECT COUNT(*) , not a SELECT COUNT(DISTINCT)
*
* Explain that even with countDistinct=true the count refers to all the elements counted,
* even if they were repeated and don't appear in {@link Count#getDistinctList()}
*
*/
public long getCount() {
return count;
}
public Set<Comparable<?>> getDistinctList() throws CounterException {
if (!countDistinct) {
throw new CounterException(
"unsupported method for counter with countDistinct=false.Please use : Counter.createWithDistinctElements() ");
}
return distinctList;
}
public List<String> getDistinctListAsStringList() throws CounterException { // Convenience
// method
if (!countDistinct) {
throw new CounterException(
"unsupported method for counter with non-distinct values. Please use : Counter.createWithDistinctElements()");
}
List<String> list = new ArrayList<String>(distinctList.size());
for (Object obj : distinctList) {
list.add(obj + "");
}
return list;
}
public Map<Object, Count> getNextLevels() {
return nextLevels;
}
public Count get(Object o) {
if (nextLevels != null) {
Count co = nextLevels.get(o);
if (co == null) {
return new Count(countDistinct);
} else {
return co;
}
} else {
return new Count(countDistinct);
}
}
}
private Count root;
private List<Object> states = new ArrayList<Object>();
public enum Type {
COUNT, DISTINCT_LIST
}
private Counter() {
root = new Count(countDistinct);
}
private Counter(boolean countDistinct) {
this();
this.countDistinct = countDistinct;
}
public Counter in(Comparable<?> o) {
states.add(o);
return this;
}
public void count(Comparable<?> o) {
// Root level
Count currentLevel = root;
c(currentLevel, o);
// Iterate over the given levels
for (Object state : states) {
if (currentLevel.getNextLevels() == null) {
currentLevel.nextLevels = Maps.newHashMap();
}
Count nextLevel = currentLevel.getNextLevels().get(state);
if (nextLevel == null) {
nextLevel = new Count(countDistinct);
currentLevel.getNextLevels().put(state, nextLevel);
}
c(nextLevel, o);
currentLevel = nextLevel;
}
states.clear();
}
public Count getCounts() {
return root;
}
private void c(Count currentLevel, Comparable<?> o) {
currentLevel.count++;
try {
if (countDistinct) {
Set<Comparable<?>> distinctSet;
distinctSet = (Set<Comparable<?>>) currentLevel.getDistinctList();
if (distinctSet == null) {
distinctSet = Sets.newTreeSet();
currentLevel.distinctList = distinctSet;
}
distinctSet.add(o);
}
} catch (Exception e) {
// this is never reached }
}
}
/**
* @param args
*/
public static void main(String args[]) {
Counter c = new Counter(true);
c.in("Animals").in("Dog").in("Mastin").count("toby");
c.in("Animals").in("Dog").in("BullDog").count("toby");
c.in("Animals").in("Dog").in("BullDog").count("brian");
//System.out.println(ObjectUtils.toIndentedJSON(c.getCounts()));
}
}