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

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

///###////////////////////////////////////////////////////////////////////////
//
// 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.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
{
    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();
        }
    }

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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy