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) 2019, 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.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.indexed.IndexedHelper;
import org.javimmutable.collections.iterators.IndexedIterator;
import org.javimmutable.collections.iterators.IteratorHelper;
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 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 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 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 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 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 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 Iterator other)
    {
        final Counter counter = new Counter(other);
        return new Editor()
            .max(counter)
            .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 Iterator other)
    {
        if (isEmpty()) {
            return this;
        } else if (!other.hasNext()) {
            return deleteAll();
        } else {
            final Counter counter = new Counter(other);
            return new Editor()
                .min(counter)
                .removeValuesNotInCounter(counter)
                .build();
        }
    }

    @Override
    @Nonnull
    public JImmutableMultiset intersection(@Nonnull JImmutableMultiset other)
    {
        if (isEmpty()) {
            return this;
        } else if (other.isEmpty()) {
            return deleteAll();
        } else {
            final Counter counter = new Counter(other.entries());
            return new Editor()
                .min(counter)
                .removeValuesNotInCounter(counter)
                .build();
        }
    }

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

    @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
    public int hashCode()
    {
        return IteratorHelper.iteratorHashCode(iterator());
    }

    @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 IteratorHelper.iteratorToString(occurrences().iterator());
    }

    public void checkInvariants()
    {
        map.checkInvariants();
        if (occurrences < map.size()) {
            throw new IllegalStateException();
        }
        int checkOccurrences = 0;
        for (JImmutableMap.Entry entry : entries()) {
            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 (JImmutableMap.Entry entry : values.entries()) {
            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 (JImmutableMap.Entry entry : values.entries()) {
            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 (JImmutableMap.Entry entry : values.entries()) {
            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 (JImmutableMap.Entry entry : other.entries()) {
            final T value = entry.getKey();
            final int otherCount = entry.getValue();
            editor.set(value, Math.max(otherCount, count(value)));
        }
        return editor.build();
    }

    private class Editor
    {
        private JImmutableMap newMap;
        private int newOccurrences;

        private Editor()
        {
            this.newMap = map;
            this.newOccurrences = occurrences;
        }

        private Editor remove(T value)
        {
            return set(value, 0);
        }

        private Editor delta(T value,
                             int delta)
        {
            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);
            adjust(value, oldCount, newCount);
            return this;
        }

        private Editor max(Counter counter)
        {
            for (Map.Entry entry : counter) {
                final T value = entry.getKey();
                final int ourCount = newMap.getValueOr(value, 0);
                final int theirCount = entry.getValue();
                adjust(value, ourCount, Math.max(ourCount, theirCount));
            }
            return this;
        }

        private Editor min(Counter counter)
        {
            for (Map.Entry entry : counter) {
                final T value = entry.getKey();
                final int ourCount = newMap.getValueOr(value, 0);
                final int theirCount = entry.getValue();
                adjust(value, ourCount, Math.min(ourCount, theirCount));
            }
            return this;
        }

        private void adjust(T value,
                            int oldCount,
                            int newCount)
        {
            if (newCount != oldCount) {
                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
        implements Iterable>
    {
        private final Map counts;

        private Counter()
        {
            counts = emptyMutableMap();
            assert counts.isEmpty();
        }

        private Counter(@Nonnull Iterator values)
        {
            this();
            while (values.hasNext()) {
                final T value = values.next();
                if (value != null) {
                    add(value, 1);
                }
            }
        }

        private  Counter(@Nonnull IterableStreamable> values)
        {
            this();
            for (JImmutableMap.Entry entry : values) {
                add(entry.getKey(), entry.getValue());
            }
        }

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

        @Nonnull
        @Override
        public Iterator> iterator()
        {
            return counts.entrySet().iterator();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy