001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.collections; 018 019import java.io.PrintWriter; 020import java.io.Serializable; 021import java.util.Collection; 022import java.util.Comparator; 023import java.util.Iterator; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.function.BiFunction; 029 030import org.conqat.lib.commons.js_export.ExportAsType; 031import org.conqat.lib.commons.js_export.ExportToJavaScript; 032 033import com.fasterxml.jackson.annotation.JsonProperty; 034 035/** 036 * This class manages a set of counters (i.e. is a mapping from some key objects 037 * to integers). As the implementation is based on hash maps, key objects must 038 * provide suitable hash keys. 039 */ 040@ExportToJavaScript 041public class CounterSet<E> implements Serializable, Iterable<Pair<E, Integer>> { 042 043 /** Version used for serialization. */ 044 private static final long serialVersionUID = 1; 045 046 /** The underlying map. */ 047 @JsonProperty("map") 048 @ExportAsType("!Object<string, number>") 049 protected final Map<E, Integer> map = new LinkedHashMap<>(); 050 051 /** Stores total value. */ 052 @JsonProperty("total") 053 protected int total = 0; 054 055 /** Constructs an empty {@link CounterSet}. */ 056 public CounterSet() { 057 // nothing to do 058 } 059 060 /** 061 * Constructs a new {@link CounterSet} from the given keys. Initializes all keys 062 * with 1. 063 */ 064 public CounterSet(Collection<E> keys) { 065 incAll(keys); 066 } 067 068 /** Constructs a CounterSet with one value. */ 069 public CounterSet(E key, int value) { 070 inc(key, value); 071 } 072 073 /** 074 * Add the given increment to an element. If the element was not present before, 075 * it is interpreted as if it was present with value 0. Returns the new value. 076 * 077 * @param key 078 * the key of the counter to increment. 079 * @param increment 080 * the increment. 081 */ 082 public int inc(E key, int increment) { 083 Integer value = map.get(key); 084 int newValue; 085 if (value == null) { 086 newValue = increment; 087 } else { 088 newValue = value + increment; 089 } 090 map.put(key, newValue); 091 092 // update total sum 093 total += increment; 094 return getValue(key); 095 } 096 097 /** 098 * Same as <code>inc(key, 1)</code>. 099 * 100 * @see #inc(Object, int) 101 */ 102 public int inc(E key) { 103 return inc(key, 1); 104 } 105 106 /** 107 * Add the given increment to the given keys. If a key was not present before, 108 * it is interpreted as if it was present with value 0. 109 * 110 * @param keys 111 * the keys of the counter to increment. 112 * @param increment 113 * the increment. 114 */ 115 public void incAll(Collection<E> keys, int increment) { 116 for (E key : keys) { 117 inc(key, increment); 118 } 119 } 120 121 /** Increments the given elements by 1 */ 122 public void incAll(Collection<E> keys) { 123 for (E key : keys) { 124 inc(key); 125 } 126 } 127 128 /** 129 * Adds the given {@link CounterSet} to this {@link CounterSet} by incrementing 130 * all keys contained from other. 131 */ 132 public void add(CounterSet<E> other) { 133 for (E key : other.getKeys()) { 134 inc(key, other.getValue(key)); 135 } 136 } 137 138 /** 139 * Removes the second CounterSet from the first one and returns a new 140 * CounterSet. 141 */ 142 public static <E> CounterSet<E> removeSecondFromFirst(CounterSet<E> first, CounterSet<E> second) { 143 CounterSet<E> merged = new CounterSet<>(); 144 for (E key : CollectionUtils.unionSet(first.getKeys(), second.getKeys())) { 145 merged.inc(key, first.getValue(key)); 146 merged.inc(key, -second.getValue(key)); 147 if (merged.getValue(key) == 0) { 148 merged.remove(key); 149 } 150 } 151 return merged; 152 } 153 154 /** 155 * Removes all keys that the given filter matches. 156 */ 157 public void removeIf(BiFunction<E, Integer, Boolean> filter) { 158 map.entrySet().removeIf(next -> filter.apply(next.getKey(), next.getValue())); 159 } 160 161 /** 162 * Remove the entry with the given key, i.e. sets its value to 0. In case the 163 * entry does not exist, nothing happens. 164 */ 165 public void remove(E key) { 166 total -= getValue(key); 167 map.remove(key); 168 } 169 170 /** Removes all entries with the given keys. */ 171 public void removeAll(Collection<E> keys) { 172 for (E key : keys) { 173 remove(key); 174 } 175 } 176 177 /** Clears the counter set. */ 178 public void clear() { 179 map.clear(); 180 total = 0; 181 } 182 183 /** 184 * Checks if an element is stored in the array. 185 */ 186 public boolean contains(E key) { 187 return map.containsKey(key); 188 } 189 190 /** 191 * Get the value for an element. If the the element is not stored in the counter 192 * <code>0</code> is returned. 193 */ 194 public int getValue(E key) { 195 Integer value = map.get(key); 196 if (value == null) { 197 return 0; 198 } 199 return value; 200 } 201 202 /** 203 * Returns the set of all elements used a keys for counters. 204 */ 205 public UnmodifiableSet<E> getKeys() { 206 return CollectionUtils.asUnmodifiable(map.keySet()); 207 } 208 209 /** Returns a list of all keys ordered by their value ascending */ 210 public List<E> getKeysByValueAscending() { 211 return CollectionUtils.sort(getKeys(), new Comparator<E>() { 212 @Override 213 public int compare(E key1, E key2) { 214 return map.get(key1).compareTo(map.get(key2)); 215 } 216 }); 217 } 218 219 /** Returns a list of all keys ordered by their value descending */ 220 public List<E> getKeysByValueDescending() { 221 return CollectionUtils.reverse(getKeysByValueAscending()); 222 } 223 224 /** Get total sum of all elements. */ 225 public int getTotal() { 226 return total; 227 } 228 229 /** Returns a collection of all values */ 230 public Collection<Integer> values() { 231 return map.values(); 232 } 233 234 /** {@inheritDoc} */ 235 @Override 236 public String toString() { 237 return map.toString(); 238 } 239 240 /** 241 * Prints the distribution of values (ascending or descending) to System.out, 242 * where each value is printed on a separate line in the form <key> : 243 * <value>. 244 * 245 * <p> 246 * <i>Example:</i><br> 247 * 248 * foo : 4 <br> 249 * bar : 2 250 * </p> 251 */ 252 public void printValueDistribution(boolean ascending) { 253 printValueDistribution(new PrintWriter(System.out), ascending); 254 } 255 256 /** 257 * Prints the distribution of values (ascending or descending) to the given 258 * stream, where each value is printed on a separate line in the form 259 * <key> : <value>. 260 * 261 * <p> 262 * <i>Example:</i><br> 263 * 264 * foo : 4 <br> 265 * bar : 2 266 * </p> 267 */ 268 public void printValueDistribution(PrintWriter writer, boolean ascending) { 269 List<E> keys = null; 270 if (ascending) { 271 keys = getKeysByValueAscending(); 272 } else { 273 keys = getKeysByValueDescending(); 274 } 275 for (E key : keys) { 276 writer.print(String.valueOf(key)); 277 writer.print(" : "); 278 writer.print(getValue(key)); 279 writer.println(); 280 } 281 writer.flush(); 282 } 283 284 /** {@inheritDoc} */ 285 @Override 286 public boolean equals(Object obj) { 287 if (obj instanceof CounterSet) { 288 @SuppressWarnings("rawtypes") 289 CounterSet other = (CounterSet) obj; 290 return map.equals(other.map); 291 } 292 return false; 293 } 294 295 /** {@inheritDoc} */ 296 @Override 297 public int hashCode() { 298 return map.hashCode(); 299 } 300 301 /** Returns the values as {@link Map}. */ 302 public Map<E, Integer> toMap() { 303 return new LinkedHashMap<>(map); 304 } 305 306 /** Returns the values as a {@link Map}, but omits keys where the value is 0. */ 307 public Map<E, Integer> toMapWithoutZeroEntries() { 308 Map<E, Integer> map = toMap(); 309 map.keySet().removeIf(key -> map.get(key) == 0); 310 return map; 311 } 312 313 /** {@inheritDoc} */ 314 @Override 315 public Iterator<Pair<E, Integer>> iterator() { 316 return new Iterator<Pair<E, Integer>>() { 317 private Iterator<Map.Entry<E, Integer>> delegate = map.entrySet().iterator(); 318 319 /** {@inheritDoc} */ 320 @Override 321 public boolean hasNext() { 322 return delegate.hasNext(); 323 } 324 325 /** {@inheritDoc} */ 326 @Override 327 public Pair<E, Integer> next() { 328 Entry<E, Integer> next = delegate.next(); 329 if (next == null) { 330 return null; 331 } 332 return new Pair<>(next.getKey(), next.getValue()); 333 } 334 }; 335 } 336}