org.javimmutable.collections.common.AbstractJImmutableMultiset Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javimmutable-collections Show documentation
Show all versions of javimmutable-collections Show documentation
Library providing immutable/persistent collection classes for
Java. While collections are immutable they provide methods for
adding and removing values by creating new modified copies of
themselves. Each copy shares almost all of its structure with
other copies to minimize memory consumption.
///###////////////////////////////////////////////////////////////////////////
//
// Burton Computer Corporation
// http://www.burton-computer.com
//
// Copyright (c) 2018, Burton Computer Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the
// distribution.
//
// Neither the name of the Burton Computer Corporation nor the names
// of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.javimmutable.collections.common;
import org.javimmutable.collections.Cursor;
import org.javimmutable.collections.IterableStreamable;
import org.javimmutable.collections.JImmutableMap;
import org.javimmutable.collections.JImmutableMultiset;
import org.javimmutable.collections.JImmutableSet;
import org.javimmutable.collections.SplitableIterator;
import org.javimmutable.collections.cursors.Cursors;
import org.javimmutable.collections.cursors.LazyMultiCursor;
import org.javimmutable.collections.cursors.StandardCursor;
import org.javimmutable.collections.indexed.IndexedHelper;
import org.javimmutable.collections.iterators.IndexedIterator;
import org.javimmutable.collections.iterators.LazyMultiIterator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@Immutable
public abstract class AbstractJImmutableMultiset
implements JImmutableMultiset
{
protected final JImmutableMap map;
protected final int occurrences;
protected AbstractJImmutableMultiset(JImmutableMap map,
int occurrences)
{
this.map = map;
this.occurrences = occurrences;
}
@Override
@Nonnull
public JImmutableMultiset insert(@Nonnull T value)
{
return new Editor().delta(value, 1).build();
}
@Nonnull
@Override
public JImmutableMultiset insert(@Nonnull T value,
int count)
{
if (count < 0) {
throw new IllegalArgumentException();
} else if (count == 0) {
return this;
} else {
return new Editor().delta(value, count).build();
}
}
@Nonnull
@Override
public JImmutableSet getInsertableSelf()
{
return this;
}
@Override
public boolean contains(@Nullable T value)
{
return (value != null) && (count(value) > 0);
}
@Override
public boolean containsAtLeast(@Nullable T value,
int count)
{
if (count < 0) {
throw new IllegalArgumentException();
} else {
return (value != null) && (count(value) >= count);
}
}
@Override
public boolean containsAll(@Nonnull Iterable extends T> other)
{
return containsAll(other.iterator());
}
@Override
public boolean containsAll(@Nonnull Cursor extends T> other)
{
return containsAll(other.iterator());
}
@Override
public boolean containsAll(@Nonnull Iterator extends T> other)
{
while (other.hasNext()) {
if (!contains(other.next())) {
return false;
}
}
return true;
}
@Override
public boolean containsAllOccurrences(@Nonnull Iterable extends T> other)
{
return containsAllOccurrences(other.iterator());
}
@Override
public boolean containsAllOccurrences(@Nonnull Cursor extends T> other)
{
return containsAllOccurrences(other.iterator());
}
@Override
public boolean containsAllOccurrences(@Nonnull Iterator extends T> other)
{
final Counter counter = new Counter();
while (other.hasNext()) {
final T value = other.next();
if ((value == null) || (counter.add(value, 1) > count(value))) {
return false;
}
}
return true;
}
@Override
public boolean containsAllOccurrences(@Nonnull JImmutableMultiset extends T> values)
{
return containsAllOccurrencesMultisetHelper(values);
}
@Override
public boolean containsAny(@Nonnull Iterable extends T> other)
{
return containsAny(other.iterator());
}
@Override
public boolean containsAny(@Nonnull Cursor extends T> other)
{
return containsAny(other.iterator());
}
@Override
public boolean containsAny(@Nonnull Iterator extends T> other)
{
while (other.hasNext()) {
if (contains(other.next())) {
return true;
}
}
return false;
}
@Override
@Nonnull
public JImmutableMultiset delete(@Nonnull T value)
{
return new Editor().remove(value).build();
}
@Override
@Nonnull
public JImmutableMultiset deleteOccurrence(@Nonnull T value)
{
return new Editor().delta(value, -1).build();
}
@Override
@Nonnull
public JImmutableMultiset deleteOccurrence(@Nonnull T value,
int subtractBy)
{
if (subtractBy < 0) {
throw new IllegalArgumentException();
} else {
return new Editor().delta(value, -subtractBy).build();
}
}
@Override
@Nonnull
public JImmutableMultiset deleteAll(@Nonnull Iterable extends T> other)
{
return deleteAll(other.iterator());
}
@Override
@Nonnull
public JImmutableMultiset deleteAll(@Nonnull Cursor extends T> other)
{
return deleteAll(other.iterator());
}
@Override
@Nonnull
public JImmutableMultiset deleteAll(@Nonnull Iterator extends T> other)
{
Editor editor = new Editor();
while (other.hasNext()) {
final T value = other.next();
if (value != null) {
editor.remove(value);
}
}
return editor.build();
}
@Override
@Nonnull
public JImmutableMultiset deleteAllOccurrences(@Nonnull Iterable extends T> other)
{
return deleteAllOccurrences(other.iterator());
}
@Override
@Nonnull
public JImmutableMultiset deleteAllOccurrences(@Nonnull Cursor extends T> other)
{
return deleteAllOccurrences(other.iterator());
}
@Override
@Nonnull
public JImmutableMultiset deleteAllOccurrences(@Nonnull Iterator extends T> other)
{
Editor editor = new Editor();
while (other.hasNext()) {
final T value = other.next();
if (value != null) {
editor.delta(value, -1);
}
}
return editor.build();
}
@Override
@Nonnull
public JImmutableMultiset deleteAllOccurrences(@Nonnull JImmutableMultiset extends T> other)
{
return deleteAllOccurrencesMultisetHelper(other);
}
@Override
@Nonnull
public JImmutableMultiset insertAll(@Nonnull Iterable extends T> values)
{
return insertAll(values.iterator());
}
@Override
@Nonnull
public JImmutableMultiset insertAll(@Nonnull Cursor extends T> values)
{
return insertAll(values.iterator());
}
@Override
@Nonnull
public JImmutableMultiset insertAll(@Nonnull Iterator extends T> other)
{
Editor editor = new Editor();
while (other.hasNext()) {
final T value = other.next();
if (value != null) {
editor.delta(value, 1);
}
}
return editor.build();
}
@Override
@Nonnull
public JImmutableMultiset insertAll(@Nonnull JImmutableMultiset extends T> values)
{
return insertAllMultisetHelper(values);
}
@Override
@Nonnull
public JImmutableMultiset union(@Nonnull Iterable extends T> other)
{
return union(other.iterator());
}
@Override
@Nonnull
public JImmutableMultiset union(@Nonnull Cursor extends T> other)
{
return union(other.iterator());
}
@Override
@Nonnull
public JImmutableMultiset union(@Nonnull Iterator extends T> other)
{
final Counter counter = new Counter();
final Editor editor = new Editor();
while (other.hasNext()) {
final T value = other.next();
if (value != null) {
final int otherCount = counter.add(value, 1);
editor.set(value, Math.max(otherCount, count(value)));
}
}
return editor.build();
}
@Override
@Nonnull
public JImmutableMultiset union(@Nonnull JImmutableMultiset extends T> other)
{
return unionMultisetHelper(other);
}
@Override
@Nonnull
public JImmutableMultiset intersection(@Nonnull Iterable extends T> other)
{
return intersection(other.iterator());
}
@Override
@Nonnull
public JImmutableMultiset intersection(@Nonnull Cursor extends T> other)
{
return intersection(other.iterator());
}
@Override
@Nonnull
public JImmutableMultiset intersection(@Nonnull Iterator extends T> other)
{
if (isEmpty()) {
return this;
} else if (!other.hasNext()) {
return deleteAll();
} else {
final Counter counter = new Counter();
final Editor editor = new Editor();
while (other.hasNext()) {
final T value = other.next();
if (value != null) {
final int otherCount = counter.add(value, 1);
editor.set(value, Math.min(otherCount, count(value)));
}
}
return editor.removeValuesNotInCounter(counter).build();
}
}
@Override
@Nonnull
public JImmutableMultiset intersection(@Nonnull JImmutableMultiset extends T> other)
{
if (isEmpty()) {
return this;
} else if (other.isEmpty()) {
return deleteAll();
} else {
return intersectionMultisetHelper(other);
}
}
@Override
@Nonnull
public JImmutableMultiset intersection(@Nonnull JImmutableSet extends T> other)
{
return intersection(other.getSet());
}
@Override
@Nonnull
public JImmutableMultiset intersection(@Nonnull Set extends T> other)
{
if (isEmpty()) {
return this;
} else if (other.isEmpty()) {
return deleteAll();
} else {
Editor editor = new Editor();
for (JImmutableMap.Entry entry : map) {
final T value = entry.getKey();
final int oldCount = entry.getValue();
if (other.contains(value)) {
editor.adjust(value, oldCount, 1);
} else {
editor.adjust(value, oldCount, 0);
}
}
return editor.build();
}
}
@Override
public int count(@Nonnull T value)
{
Conditions.stopNull(value);
return map.getValueOr(value, 0);
}
@Nonnull
@Override
public JImmutableMultiset setCount(@Nonnull T value,
int count)
{
Conditions.stopNull(value, count);
if (count < 0) {
throw new IllegalArgumentException();
} else {
return new Editor().set(value, count).build();
}
}
/**
* Implemented by derived classes to create a new instance of the appropriate class.
*
* @param map base map for new multiset
* @param occurrences total occurrences in map
* @return new multiset built from map
*/
protected abstract JImmutableMultiset create(JImmutableMap map,
int occurrences);
/**
* Implemented by derived classes to create a new empty mutable Map
* that operates in the same way as this multiset's underlying immutable Map.
*
* @return new empty Counter
*/
protected abstract Map emptyMutableMap();
@Override
public boolean isEmpty()
{
return map.isEmpty();
}
@Override
public int size()
{
return map.size();
}
@Override
public int occurrenceCount()
{
return occurrences;
}
@Override
@Nonnull
public Set getSet()
{
return SetAdaptor.of(this);
}
@Override
@Nonnull
public SplitableIterator iterator()
{
return map.keys().iterator();
}
@Override
public int getSpliteratorCharacteristics()
{
return map.getSpliteratorCharacteristics();
}
@Override
@Nonnull
public Cursor occurrenceCursor()
{
return LazyMultiCursor.transformed(entryCursor(), entry -> () -> StandardCursor.repeating(entry.getValue(), entry.getKey()));
}
@Nonnull
@Override
public IterableStreamable> entries()
{
return map;
}
@Nonnull
@Override
public IterableStreamable occurrences()
{
return new IterableStreamable()
{
@Nonnull
@Override
public SplitableIterator iterator()
{
return LazyMultiIterator.transformed(map.iterator(), e -> () -> IndexedIterator.iterator(IndexedHelper.repeating(e.getKey(), e.getValue())));
}
@Override
public int getSpliteratorCharacteristics()
{
return map.getSpliteratorCharacteristics();
}
};
}
@Override
@Nonnull
public Cursor cursor()
{
return map.keysCursor();
}
@Override
@Nonnull
public Cursor> entryCursor()
{
return map.cursor();
}
@Override
public int hashCode()
{
return Cursors.computeHashCode(occurrenceCursor());
}
@Override
public boolean equals(Object o)
{
if (o == this) {
return true;
} else if (o == null) {
return false;
} else if (o instanceof JImmutableMultiset) {
final JImmutableMultiset that = (JImmutableMultiset)o;
//noinspection unchecked
return (occurrenceCount() == that.occurrenceCount()) && containsAllOccurrences(that);
} else if (o instanceof JImmutableSet) {
final JImmutableSet that = (JImmutableSet)o;
return (size() == occurrences) && getSet().equals(that.getSet());
} else {
return (o instanceof Set) && (size() == occurrences) && getSet().equals(o);
}
}
@Override
public String toString()
{
return Cursors.makeString(occurrenceCursor());
}
public void checkInvariants()
{
map.checkInvariants();
if (occurrences < map.size()) {
throw new IllegalStateException();
}
int checkOccurrences = 0;
for (JImmutableMap.Entry entry : entryCursor()) {
int entryCount = entry.getValue();
if (entryCount <= 0) {
throw new IllegalStateException(String.format("illegal count of %d for value %s%n", entryCount, entry.getKey()));
}
checkOccurrences += entryCount;
}
if (occurrences != checkOccurrences) {
throw new RuntimeException(String.format("occurrence size mismatch - expected %d found %d%n", checkOccurrences, occurrences));
}
}
private boolean containsAllOccurrencesMultisetHelper(@Nonnull JImmutableMultiset values)
{
for (Cursor> e = values.entryCursor().start(); e.hasValue(); e = e.next()) {
final JImmutableMap.Entry entry = e.getValue();
final T value = entry.getKey();
final int otherCount = entry.getValue();
if (count(value) < otherCount) {
return false;
}
}
return true;
}
private JImmutableMultiset deleteAllOccurrencesMultisetHelper(@Nonnull JImmutableMultiset values)
{
final Editor editor = new Editor();
for (Cursor> e = values.entryCursor().start(); e.hasValue(); e = e.next()) {
final JImmutableMap.Entry entry = e.getValue();
final T value = entry.getKey();
final int otherCount = entry.getValue();
editor.delta(value, -otherCount);
}
return editor.build();
}
private JImmutableMultiset insertAllMultisetHelper(@Nonnull JImmutableMultiset values)
{
final Editor editor = new Editor();
for (Cursor> e = values.entryCursor().start(); e.hasValue(); e = e.next()) {
final JImmutableMap.Entry entry = e.getValue();
final T value = entry.getKey();
final int otherCount = entry.getValue();
editor.delta(value, otherCount);
}
return editor.build();
}
@Nonnull
private JImmutableMultiset unionMultisetHelper(@Nonnull JImmutableMultiset other)
{
final Editor editor = new Editor();
for (Cursor> e = other.entryCursor().start(); e.hasValue(); e = e.next()) {
final JImmutableMap.Entry entry = e.getValue();
final T value = entry.getKey();
final int otherCount = entry.getValue();
editor.set(value, Math.max(otherCount, count(value)));
}
return editor.build();
}
@Nonnull
private JImmutableMultiset intersectionMultisetHelper(@Nonnull JImmutableMultiset other)
{
final Counter counter = new Counter();
final Editor editor = new Editor();
for (Cursor> e = other.entryCursor().start(); e.hasValue(); e = e.next()) {
final JImmutableMap.Entry entry = e.getValue();
final T value = entry.getKey();
final int otherCount = counter.add(value, entry.getValue());
editor.set(value, Math.min(otherCount, count(value)));
}
return editor.removeValuesNotInCounter(counter).build();
}
private class Editor
{
private JImmutableMap newMap;
private int newOccurrences;
private Editor()
{
this.newMap = map;
this.newOccurrences = occurrences;
}
private Editor remove(T value)
{
final Integer oldCount = newMap.get(value);
if (oldCount != null) {
newMap = newMap.delete(value);
newOccurrences -= oldCount;
}
return this;
}
private Editor delta(T value,
int delta)
{
if (delta != 0) {
final int oldCount = newMap.getValueOr(value, 0);
adjust(value, oldCount, oldCount + delta);
}
return this;
}
private Editor set(T value,
int newCount)
{
final int oldCount = newMap.getValueOr(value, 0);
if (newCount != oldCount) {
adjust(value, oldCount, newCount);
}
return this;
}
private void adjust(T value,
int oldCount,
int newCount)
{
if (newCount <= 0) {
newMap = newMap.delete(value);
newOccurrences -= oldCount;
} else {
newMap = newMap.assign(value, newCount);
newOccurrences = newOccurrences - oldCount + newCount;
}
}
private Editor removeValuesNotInCounter(Counter counter)
{
for (JImmutableMap.Entry entry : newMap) {
if (counter.get(entry.getKey()) == 0) {
newMap = newMap.delete(entry.getKey());
newOccurrences -= entry.getValue();
}
}
return this;
}
private JImmutableMultiset build()
{
return (map == newMap) ? AbstractJImmutableMultiset.this : create(newMap, newOccurrences);
}
}
private class Counter
{
private final Map counts;
private Counter()
{
counts = emptyMutableMap();
assert counts.isEmpty();
}
private int add(T value,
int number)
{
assert value != null;
assert number > 0;
final int currentCount = get(value);
final int newCount = currentCount + number;
counts.put(value, newCount);
return newCount;
}
private int get(T value)
{
assert value != null;
final Integer count = counts.get(value);
assert (count == null) || (count > 0);
return (count == null) ? 0 : count;
}
}
}