/////////////////////////////////////////////////////// MOVED TO : https://github.com/claudemartin/multiset /////////////////////////////////////////////////////// package ch.claude_martin; import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Map.Entry; import java.util.function.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.concurrent.NotThreadSafe; /** * Multiset in Java. It doesn't maintain sorting or insertion order. But it's not a regular set, * because you can add an element more than once. If an element is already present that equals the * element to be inserted then it's multiplicity (count) is incremented by one. null is * allowed as an element. * *

* An element with multiplicity zero is completely removed from the data structure and will not * occur as a tuple of {@code (element, 0)}. This needs to be considered when using methods such as * {@link #setMultiplicities(ToIntBiFunction)}, {@link #forEach(ObjIntConsumer)}, {@link #entries()} * , and {@link #merge(Multiset, IntBinaryOperator)}. * *

* blog entry *

* Copyright: You can use this for whatever you like! No restrictions. * * @author Claude Martin * * @param * The type of the elements */ @NotThreadSafe public final class Multiset extends AbstractCollection implements Serializable { private static final long serialVersionUID = -7083567870279959503L; /** The backing map. */ final Map map; /** View as map. Lazy. This is the same as {@link #map} if and only if this is unmodifiable. */ private transient Map view = null; /** * Total size of this multiset. * * @implNote Modification must always be performend on the {@link #map} first. This will fail if * this multiset {@link #isUnmodifiable() is unmodifiable}. */ int size = 0; public Multiset() { this.map = new HashMap<>(); } /** Creates an unmodifiable Multiset. */ private Multiset(final Multiset multiset, final boolean unmodifiable) { assert unmodifiable; this.map = this.view = Collections.unmodifiableMap(multiset.map); this.size = multiset.size; this.checkSize(); return; } /** Creates an empty Multiset. */ private Multiset(final boolean empty) { this.map = this.view = Collections.emptyMap(); this.size = 0; } /** Creates a new Multiset and adds all elements from the given collection. */ public Multiset(final Collection coll) { requireNonNull(coll, "coll"); this.map = new HashMap<>(coll.size()); for (final T t : coll) this.map.merge(t, 1, Integer::sum); this.size = coll.size(); this.checkSize(); } /** Creates a new Multiset and adds all elements from the given Multiset. */ public Multiset(final Multiset multiset) { requireNonNull(multiset, "multiset"); multiset.checkSize(); this.map = new HashMap<>(multiset.map); this.size = multiset.size; this.checkSize(); } /** * Creates a new {@link Multiset} containing all given elements. * * @param elements * The elements * @return new Multiset<>(asList(elements)); */ @SafeVarargs public static Multiset of(final T... elements) { requireNonNull(elements, "elements"); return new Multiset<>(asList(elements)); } /** * Creates a new {@link Multiset} containing all given elements, with the multiplicity defined by * the given function. * * @param set * The elements * @param m * The multiplicity * @return new {@link Multiset} greated from the given data. */ public static Multiset of(final Set set, final ToIntFunction m) { requireNonNull(set, "set"); requireNonNull(m, "m"); final Multiset result = new Multiset<>(); for (final T t : set) result.setMultiplicity(t, m.applyAsInt(t)); return result; } /** * Adds the object and returns true. * * @see #insert(Object) */ @Override public boolean add(final T t) { this.insert(t); return true; } /** * Sets the multiplicity of a certain key. The key is removed if m is 0 or added if it was 0. * Therefore this may lead to a modification of the underlying data structure. The * {@link #iterator() Iterator} supports @link {@link Iterator#remove() removal} of elements * during iteration. * * @param element * the element * @param m * new multiplicity * @returns the old multiplicity. */ public int setMultiplicity(final T element, final int m) { if (m < 0) throw new IllegalArgumentException("m < 0"); final int i = this.map.getOrDefault(element, 0); if (m == i) return i; if (m == 0) this.map.remove(element); else this.map.put(element, m); this.size = this.size - i + m; this.checkSize(); return i; } /** * Sets the multiplicity of a certain key, only if currently set to the specified value. * * @param element * the element * @param oldMultiplicity * old multiplicity * @param newMultiplicity * new multiplicity * * @return {@code true} if the value was replaced * * @see Map#replace(Object, Object, Object) */ public boolean setMultiplicity(final T element, final int oldMultiplicity, final int newMultiplicity) { requireNonNull(element, "obj"); if (oldMultiplicity < 0 || newMultiplicity < 0) throw new IllegalArgumentException("negative multiplicity"); if (this.getMultiplicity(element) == oldMultiplicity) { this.setMultiplicity(element, newMultiplicity); return true; } return false; } /** * Adds the object and returns the new multiplicity. * * @see #add(Object) */ public int insert(final T t) { final Integer result = this.map.merge(t, 1, Integer::sum); this.size++; this.checkSize(); return result; } /** * Removes a single instance of the specified element from this multiset. This decrements the * multiplicity by one. Does nothing if it wasn't in the set. To remove all equal objects you must * call {@link #setMultiplicity(Object, int) setMultiplicity(t, 0)}. */ @Override @SuppressWarnings("unchecked") public boolean remove(final Object t) { final int value = this.map.getOrDefault(t, 0); switch (value) { case 0: return false; case 1: this.map.remove(t); break; default: assert value > 1 : "invalid value"; this.map.put((T) t, value - 1); break; } this.size--; this.checkSize(); return true; } /** * Reset the multiplicity for each element in this {@link Multiset}. * *

* Note that elements with a multiplicity of zero will not be processed, as they are not contained * in the multiset. * * @param m * function for new multiplicities * @return true, if any multiplicity of any element was altered. */ public boolean setMultiplicities(final ToIntBiFunction m) { requireNonNull(m, "m"); if (this.isEmpty()) return false; boolean modified = false; final Iterator> itr = this.map.entrySet().iterator(); while (itr.hasNext()) { final Entry next = itr.next(); final T t = next.getKey(); final int oldm = next.getValue(); final int newM = m.applyAsInt(t, oldm); if (oldm != newM) { if (newM == 0) itr.remove(); else next.setValue(newM); this.size += -oldm + newM; modified = true; } } this.checkSize(); return modified; } @SuppressWarnings({ "unchecked" }) @Override public boolean removeAll(final Collection c) { requireNonNull(c, "c"); if (c instanceof Multiset) return this.removeAll((Multiset) c); return super.removeAll(c); } /** * @see #removeAll(Collection) */ public boolean removeAll(final Multiset set) { requireNonNull(set, "set"); boolean result = false; if (set.isEmpty()) return false; final Iterator> itr = this.map.entrySet().iterator(); while (itr.hasNext()) { final Entry next = itr.next(); if (set.map.containsKey(next.getKey())) { itr.remove(); this.size -= next.getValue(); result = true; } } this.checkSize(); return result; } @Override public boolean retainAll(final Collection c) { requireNonNull(c, "c"); if (c instanceof Multiset) return this.retainAll((Multiset) c); return this.asSet().retainAll(c); } /** * @see #retainAll(Collection) */ public boolean retainAll(final Multiset set) { requireNonNull(set, "set"); if (set == this) return false; return this.asSet().retainAll(set.asSet()); } /** * Get the multiplicity or the given object. If the object is not in this multiset then the * multiplicity is 0. * * @return the multiplicity. */ public int getMultiplicity(final Object t) { return this.map.getOrDefault(t, 0); } Iterator iterator(final Iterator> entries) { return new Iterator() { int c = 0; T t = null; @Override public T next() { if (!this.hasNext()) throw new NoSuchElementException(); if (this.c == 0) { final Entry e = entries.next(); this.t = e.getKey(); this.c = e.getValue(); } this.c--; return this.t; } @Override public boolean hasNext() { return this.c > 0 || entries.hasNext(); } @Override public void remove() { final int multiplicity = Multiset.this.getMultiplicity(this.t); if (multiplicity == 1) { entries.remove(); Multiset.this.size--; } else Multiset.this.setMultiplicity(this.t, multiplicity - 1); } }; } /** * Iterator of all elements. Each element is iterated as many times as it is in this multiset. The * {@link Iterator} supports the {@link Iterator#remove() remove} method. */ @Override public Iterator iterator() { this.checkSize(); return this.iterator(this.map.entrySet().iterator()); } @Override public Spliterator spliterator() { return Spliterators.spliterator(this, 0); } /** The class invariant. */ void checkSize() { assert this.map.values().stream().mapToInt(i -> i).sum() == this.size : this.getClass() .getSimpleName() + ": wrong size"; } /** * {@inheritDoc} *

* This returns the total number of elements, including repeated memberships (cardinality). To get * the amount of discinct elements use {@link #asMap()}.{@link Map#size() size()}. * */ @Override public int size() { this.checkSize(); return this.size; } /** * Returns a copy of the underlying set of elements. Changes to this set do not alter the * Multiset. * *

* If using the formal definition (A, m) of a multiset, the returned set is * A and multiset::getMultiplicity is m. * * @see #asSet() * @see #entries() */ public Set toSet() { return new HashSet<>(this.map.keySet()); } /** * Conventiance mehtod to create a string representation of this multiset. *

* Example: {@code ms.toString((t, i) -> t + " x " + i, ", ", "[ ", " ]")} * * @param f * Mapping function for each entry (element and multiplicity) * @param delimiter * the delimiter to be used between each entry * @param prefix * the sequence of characters to be used at the beginning of the joined result * @param suffix * the sequence of characters to be used at the end of the joined result * @return string representation of this multiset */ public String toString(final BiFunction f, final CharSequence delimiter, final CharSequence prefix, final CharSequence suffix) { requireNonNull(f, "f"); requireNonNull(delimiter, "delimiter"); requireNonNull(prefix, "prefix"); requireNonNull(suffix, "suffix"); return this.map.entrySet().stream().map(e -> f.apply(e.getKey(), e.getValue())) .collect(Collectors.joining(delimiter, prefix, suffix)); } /** * As a new, sorted List. * *

* To sort by multiplicity, descending:
* multiset.toList((a, b) -> b.getValue() - a.getValue()) */ public List toList(final Comparator> comparator) { if (null == comparator) return this.stream().collect(Collectors.toList()); return this.entries().sorted(comparator) .flatMap(e -> IntStream.range(0, e.getValue()). mapToObj(x -> e.getKey())) .collect(Collectors.toList()); } /** * Streams each element and it's multiplicity. The returned entries are immutable. Mutable entries * can be accessed using: {@code this.asMap().entrySet()} */ @SuppressWarnings("unchecked") public Stream> entries() { final Stream> stream = this.map.entrySet().stream(); if (this.isUnmodifiable()) return stream; return stream.map(SimpleImmutableEntry::new); } @Override public boolean addAll(final Collection c) { requireNonNull(c, "c"); if (c instanceof Multiset) return this.addAll((Multiset) c); return super.addAll(c); } /** @see #addAll(Collection) */ public boolean addAll(final Multiset ms) { for (final Entry e : ms.map.entrySet()) { final Integer val = e.getValue(); this.map.merge(e.getKey(), val, Integer::sum); this.size += val; } this.checkSize(); return ms.size() != 0; } /** * Two {@link Multiset}s are equal if both contain the same elements with the same multiplicities. */ @Override public boolean equals(final Object obj) { if (obj instanceof Multiset) { final Multiset other = (Multiset) obj; return this.size == other.size && this.map.equals(other.map); } return false; } @Override public int hashCode() { return this.size ^ this.map.hashCode(); } @Override public void clear() { if (this.size == 0) return; this.map.clear(); this.size = 0; this.checkSize(); } /** * Creates a clone. The cloned multiset is modifiable. */ @Override protected Object clone() { return new Multiset<>(this); } @Override public boolean containsAll(final Collection c) { requireNonNull(c, "c"); if (c instanceof Multiset) this.map.keySet().containsAll(((Multiset) c).map.keySet()); return this.map.keySet().containsAll(c); } @Override public boolean isEmpty() { return this.size == 0; } /** Process each object only once, with the multiplicity given as a second parameter. */ public void forEach(final ObjIntConsumer action) { this.map.forEach(action::accept); } @Override public boolean contains(final Object obj) { return this.map.containsKey(obj); } /** * The intersection of two multisets. This is equal to * this.{@link #merge(Multiset, IntBinaryOperator) merge}(set, {@link Math#min(int, int) Math::min}) * . However, the type of the elements in the provided multiset does not matter. */ @SuppressWarnings("unchecked") public Multiset intersect(final Multiset set) { requireNonNull(set, "set"); return this.merge((Multiset) set, Math::min); } /** * The union of two multisets. This is equal to * this.{@link #merge(Multiset, IntBinaryOperator) merge}(set, {@link Integer#sum(int, int) Integer::sum}) * *

* Note: A different definition of union would use {@link Math#max(int, int)} with * {@link #merge(Multiset, IntBinaryOperator)}. * * @see #addAll(Collection) */ public Multiset union(final Multiset set) { requireNonNull(set, "set"); return this.merge(set, Integer::sum); } public Multiset union(final Multiset set, final Class supertype) { requireNonNull(set, "set"); requireNonNull(supertype, "supertype"); return this.merge(set, Integer::sum, supertype); } /** * The relative complement of this and a given multiset. * * @see #removeAll(Collection) */ public Multiset minus(final Multiset set) { requireNonNull(set, "set"); final Multiset result = new Multiset<>(this); if (set.isEmpty()) return result; result.removeAll(set); return result; } /** * Create new multiset from this and another multiset. The given operation will calculate the new * multiplicity for all elements. * *

* This pseudo-code illustrates how the operation is used to create a new multiset. In this case * the result is equal to the result of {@link #union(Multiset)}.

   * mset1 = [ a, a, a, b ];
   * mset2 = [ b, c ];
   * union = m1.merge(m2, Integer::sum);
   * // m3 = [ a, a, a, b, b, c ]
   * 
*/ public Multiset merge(final Multiset set, final IntBinaryOperator operation) { requireNonNull(set, "set"); requireNonNull(operation, "operation"); final Multiset result = new Multiset<>(this); if (set.isEmpty() && this.isEmpty()) return result; Stream.concat(this.map.keySet().stream(), set.map.keySet().stream()).distinct().forEach(t -> { result.setMultiplicity(t, // operation.applyAsInt(this.getMultiplicity(t), set.getMultiplicity(t))); }); result.checkSize(); return result; } /** * Create new multiset from this and another multiset. The given operation will calculate the new * multiplicity for all elements. * * @implNote Only with assertions enabled there will be a check that all elements are assignable * to the given supertype. * @param set * The other multiset * @param operation * the operation for the multiplicities * @param supertype * a supertype of both multisets */ @SuppressWarnings("unchecked") public Multiset merge(final Multiset set, final IntBinaryOperator operation, final Class supertype) { requireNonNull(set, "set"); requireNonNull(operation, "operation"); requireNonNull(supertype, "supertype"); assert this.asSet().stream().allMatch(e -> supertype.isAssignableFrom(e.getClass())); assert set.asSet().stream().allMatch(e -> supertype.isAssignableFrom(e.getClass())); return ((Multiset) this).merge(set, operation); } private void readObject(final java.io.ObjectInputStream stream) throws Exception { requireNonNull(stream, "stream").defaultReadObject(); this.checkSize(); } /** * Returnes an unmodifiable view of the specified {@link Multiset}. * * @param multiset * the multiset for which an unmodifiable view is to be returned. * @return an unmodifiable view of the specified map. */ public static Multiset unmodifiableMultiset(final Multiset multiset) { requireNonNull(multiset, "multiset"); if (multiset.isUnmodifiable()) return multiset; if (multiset.isEmpty()) return emptyMultiset(); return new Multiset<>(multiset, true); } /** * Checks whether this is backed by an unmodifiable map. An unmodifiable {@link Multiset} always * has the same reference set for {@link #map} and {@link #view}. * * @see #unmodifiableMultiset(Multiset) */ public boolean isUnmodifiable() { return this.map == this.view; } /** * A map-view of this {@link Multiset}. Changes are reflected on the {@link Multiset}. However, * when using streams or iterators it is not allowed to set the multiplicity to 0. Instead the * entry needs to be {@link Iterator#remove() removed}. *

* Methods such as {@link Map#put}, {@link Map#clear}, and {@link Map#contains} delegated to the * backing map of the {@link Multiset}. * * @see #asSet() * @see #entries() * */ public Map asMap() { if (this.view != null) return this.view; return this.view = new AbstractMap() { private Set> entrySet; @Override public Integer put(final T key, final Integer value) { return Multiset.this.setMultiplicity(key, value); } @Override public Set> entrySet() { if (this.entrySet != null) return this.entrySet; return this.entrySet = new AbstractSet>() { @Override public Iterator> iterator() { final Iterator> itr = Multiset.this.map.entrySet() .iterator(); return new Iterator>() { private Entry last = null; @Override public boolean hasNext() { return itr.hasNext(); } @Override public java.util.Map.Entry next() { return this.last = new Entry() { final Entry next = itr.next(); final T t = this.next.getKey(); int v = this.next.getValue(); @Override public T getKey() { return this.t; } @Override public Integer getValue() { return this.v; } @Override public Integer setValue(final Integer value) { requireNonNull(value, "value"); final int val = value.intValue(); if (val < 0) throw new IllegalArgumentException("value=" + val); if (val == 0) throw new IllegalArgumentException( "Can't set multiplicity to 0. Use remove() instead!"); final int old = this.v; Multiset.this.setMultiplicity(this.t, val); this.v = val; return old; } @Override public final int hashCode() { return Objects.hashCode(this.t) ^ this.v; } @Override public final boolean equals(final Object o) { if (o == this) return true; if (o instanceof Map.Entry) { final Map.Entry e = (Map.Entry) o; if (Objects.equals(this.t, e.getKey()) && Objects.equals(this.v, e.getValue())) return true; } return false; } @Override public String toString() { return this.t + "=" + this.v; } }; } @Override public void remove() { if (this.last == null) throw new NoSuchElementException(); itr.remove(); Multiset.this.size -= this.last.getValue(); Multiset.this.checkSize(); } }; } @Override public int size() { return Multiset.this.map.size(); } @Override public boolean isEmpty() { return Multiset.this.map.isEmpty(); } @Override public boolean contains(final Object o) { return Multiset.this.map.keySet().contains(o); } @Override public boolean add(final java.util.Map.Entry e) { // NOT SUPPORTED!!! return Multiset.this.map.entrySet().add(e); } @Override public boolean remove(final Object o) { return Multiset.this.map.entrySet().remove(o); } @Override public boolean containsAll(final Collection c) { return Multiset.this.map.entrySet().containsAll(c); } @Override public void clear() { Multiset.this.map.clear(); } }; } }; } /** * Returns a set of all elements, without multiplicity. The set is backed by the multiset, so * changes to the multiset are reflected in the set, and vice-versa. Removing one element from the * set removes all equal elements from the multiset. It does not support the add or * addAll. * *

* If using the formal definition (A, m) of a multiset, the returned set is * A and multiset::getMultiplicity is m. * * @see #toSet() * @see #asMap() * @see #entries() * @return set-view of all elements */ public Set asSet() { return this.asMap().keySet(); } /** * Creates a synchronized (thread-safe) collection and a synchronized map, both backed by the * specified multiset. The collection behaves just like the specified multiset, while the map * corresponds to {@link #asMap()}. Both data structures are modifiable and use the created * collection as the mutex for synchronization. * * @see Collections#synchronizedCollection(Collection) * * @param set * the multiset * @param consumer * accepts the collection and the map. * @throws RuntimeException * if the security manager does not allow to create a SynchronizedMap by reflection. */ @SuppressWarnings("unchecked") public static void synchronizedMultiset(final Multiset set, final BiConsumer, Map> consumer) throws RuntimeException { requireNonNull(set, "set"); requireNonNull(consumer, "consumer"); try { final Collection collection = Collections.synchronizedCollection(set); final Class> cls = (Class>) Class .forName("java.util.Collections$SynchronizedMap"); final Constructor> ctor = // cls.getDeclaredConstructor(Map.class, Object.class); ctor.setAccessible(true); final Map map = ctor.newInstance(set.asMap(), collection); consumer.accept(collection, map); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException ex) { throw new RuntimeException("Can't create synchronized Multiset.", ex); } } static Multiset emptyMultiset; /** Returns an empty multiset (unmodifiable). */ @SuppressWarnings("unchecked") public static Multiset emptyMultiset() { if (null == emptyMultiset) return (Multiset) (emptyMultiset = new Multiset(true)); return (Multiset) emptyMultiset; } }