All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.javimmutable.collections.common.AbstractJImmutableMultiset Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.2.1
Show newest version
///###////////////////////////////////////////////////////////////////////////
//
// Burton Computer Corporation
// http://www.burton-computer.com
//
// Copyright (c) 2017, 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.Cursorable;
import org.javimmutable.collections.Func1;
import org.javimmutable.collections.JImmutableMap;
import org.javimmutable.collections.JImmutableMultiset;
import org.javimmutable.collections.JImmutableSet;
import org.javimmutable.collections.cursors.Cursors;
import org.javimmutable.collections.cursors.MultiTransformCursor;
import org.javimmutable.collections.cursors.StandardCursor;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

@Immutable
public abstract class AbstractJImmutableMultiset
    implements JImmutableMultiset
{
    private final JImmutableMap map;
    private 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();
        }
    }

    @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 Cursorable other)
    {
        return containsAll(other.cursor().iterator());
    }

    @Override
    public boolean containsAll(@Nonnull Collection other)
    {
        return containsAll(other.iterator());
    }

    @Override
    public boolean containsAll(@Nonnull Cursor other)
    {
        return containsAll(other.iterator());
    }

    @Override
    public boolean containsAll(@Nonnull Iterator other)
    {
        while (other.hasNext()) {
            if (!contains(other.next())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean containsAllOccurrences(@Nonnull Cursorable other)
    {
        return containsAllOccurrences(other.cursor().iterator());
    }

    @Override
    public boolean containsAllOccurrences(@Nonnull Collection other)
    {
        return containsAllOccurrences(other.iterator());
    }

    @Override
    public boolean containsAllOccurrences(@Nonnull Cursor other)
    {
        return containsAllOccurrences(other.iterator());
    }

    @Override
    public boolean containsAllOccurrences(@Nonnull Iterator 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 values)
    {
        return containsAllOccurrencesMultisetHelper(values);
    }

    @Override
    public boolean containsAny(@Nonnull Cursorable other)
    {
        return containsAny(other.cursor().iterator());
    }

    @Override
    public boolean containsAny(@Nonnull Collection other)
    {
        return containsAny(other.iterator());
    }

    @Override
    public boolean containsAny(@Nonnull Cursor other)
    {
        return containsAny(other.iterator());
    }

    @Override
    public boolean containsAny(@Nonnull Iterator 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 Cursorable other)
    {
        return deleteAll(other.cursor().iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset deleteAll(@Nonnull Collection other)
    {
        return deleteAll(other.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset deleteAll(@Nonnull Cursor other)
    {
        return deleteAll(other.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset deleteAll(@Nonnull Iterator 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 Cursorable other)
    {
        return deleteAllOccurrences(other.cursor().iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset deleteAllOccurrences(@Nonnull Collection other)
    {
        return deleteAllOccurrences(other.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset deleteAllOccurrences(@Nonnull Cursor other)
    {
        return deleteAllOccurrences(other.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset deleteAllOccurrences(@Nonnull Iterator 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 other)
    {
        return deleteAllOccurrencesMultisetHelper(other);
    }

    @Override
    @Nonnull
    public JImmutableMultiset insertAll(@Nonnull Cursorable values)
    {
        return insertAll(values.cursor().iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset insertAll(@Nonnull Collection values)
    {
        return insertAll(values.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset insertAll(@Nonnull Cursor values)
    {
        return insertAll(values.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset insertAll(@Nonnull Iterator 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 values)
    {
        return insertAllMultisetHelper(values);
    }

    @Override
    @Nonnull
    public JImmutableMultiset union(@Nonnull Cursorable other)
    {
        return union(other.cursor().iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset union(@Nonnull Collection other)
    {
        return union(other.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset union(@Nonnull Cursor other)
    {
        return union(other.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset union(@Nonnull Iterator 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 other)
    {
        return unionMultisetHelper(other);
    }

    @Override
    @Nonnull
    public JImmutableMultiset intersection(@Nonnull Cursorable other)
    {
        return intersection(other.cursor().iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset intersection(@Nonnull Collection other)
    {
        return intersection(other.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset intersection(@Nonnull Cursor other)
    {
        return intersection(other.iterator());
    }

    @Override
    @Nonnull
    public JImmutableMultiset intersection(@Nonnull Iterator 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 other)
    {
        if (isEmpty()) {
            return this;
        } else if (other.isEmpty()) {
            return deleteAll();
        } else {
            return intersectionMultisetHelper(other);
        }
    }

    @Override
    @Nonnull
    public JImmutableMultiset intersection(@Nonnull JImmutableSet other)
    {
        return intersection(other.getSet());
    }

    @Override
    @Nonnull
    public JImmutableMultiset intersection(@Nonnull Set 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 Iterator iterator()
    {
        return IteratorAdaptor.of(cursor());
    }

    @Override
    @Nonnull
    public Cursor occurrenceCursor()
    {
        return MultiTransformCursor.of(entryCursor(), new Func1, Cursor>()
        {
            @Override
            public Cursor apply(JImmutableMap.Entry entry)
            {
                return StandardCursor.of(new StandardCursor.RepeatingValueCursorSource(entry));
            }
        });
    }

    @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;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy