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

com.github.protobufel.multikeymap.Collectors Maven / Gradle / Ivy

Go to download

Java 8 implementation of the multi-key map. It behaves like a regular generic Map with the additional ability of getting its values by any combination of partial keys.

The newest version!
/*
 *    Copyright 2017 David Tesler
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 *
 */

package com.github.protobufel.multikeymap;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentHashMap.KeySetView;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.util.Comparator.comparingInt;

/**
 * The utility class providing the MultiKeyMap and multiple sets' intersection related collectors.
 *
 * @author David Tesler
 */
public final class Collectors {
    private Collectors() {
    }

    /**
     * Returns the Optional result of the intersection of all sets in the supplied Iterable.
     *
     * @param       the type of the elements of the sets
     * @param source   the Iterable of the sets to intersect with each other
     * @param parallel use parallel processing if true, sequential if false
     * @return the result of the intersection wrapped in Optional, or the empty Optional.
     */
    public static  Set intersectSets(final Iterable> source,
                                           final boolean parallel) {
        return streamOf(Objects.requireNonNull(source), parallel)
                .min(comparingInt(set -> Objects.requireNonNull(set).size()))
                .map(smallestSet -> streamOf(source, parallel).collect(setIntersecting(smallestSet, parallel)))
                .orElse(Collections.emptySet());
    }

    static  Stream streamOf(final Iterable source, final boolean parallel) {
        if (source instanceof Collection) {
            final Collection collection = (Collection) source;
            return (parallel ? collection.parallelStream() : collection.stream()).unordered();
        } else {
            return StreamSupport.stream(Objects.requireNonNull(source).spliterator(), parallel)
                    .unordered();
        }
    }

    /**
     * Gets a collector which intersects the stream of sets and returns the resulting set.
     *
     * @param smallestSet the smallest in size element of the stream of sets, any if there are several
     *                    of the same size
     * @param parallel    use parallel processing if true, sequential if false
     * @param          the type of the elements of the sets
     * @return the collector to perform the intersection of all elements of the stream of sets
     */
    public static  Collector, Set, Set> setIntersecting(final Set smallestSet,
                                                                        final boolean parallel) {
        return setIntersecting(() -> smallestSet, parallel);
    }

    /**
     * Gets a collector which intersects the stream of sets and returns the resulting set.
     *
     * @param smallestSetSupplier the supplier of the smallest in size element of the stream of sets,
     *                            any if there are several of the same size
     * @param parallel            use parallel processing if true, sequential if false
     * @param                  the type of the elements of the sets
     * @return the collector to perform the intersection of all elements of the stream of sets
     */
    public static  Collector, Set, Set> setIntersecting(
            final Supplier> smallestSetSupplier, final boolean parallel) {
        return parallel ? new ConcurrentSetIntersecting<>(smallestSetSupplier)
                : new SequentialSetIntersecting<>(smallestSetSupplier);
    }

    /**
     * Gets a MultiKeyMap producing collector.
     *
     * @param keyMapper   the key producing mapping function
     * @param valueMapper the value producing mapping function
     * @param          the type of the stream elements
     * @param          the type of the sub-keys of the MultiKeyMap
     * @param          the type of the keys of the MultiKeyMap
     * @param          the type of the values of the MultiKeyMap
     * @return the MultiKeyMap producing collector
     */
    public static , V> Collector> toMultiKeyMap(
            final Function keyMapper,
            final Function valueMapper) {
        return java.util.stream.Collectors.toMap(keyMapper, valueMapper, (k, v) -> {
            throw new IllegalStateException(String.format("duplicate key %s", k));
        }, MultiKeyMaps::newMultiKeyMap);
    }

    /**
     * Gets a MultiKeyMap producing collector.
     *
     * @param keyMapper     the key producing mapping function
     * @param valueMapper   the value producing mapping function
     * @param mergeFunction the value merging function
     * @param            the type of the stream elements
     * @param            the type of the sub-keys of the MultiKeyMap
     * @param            the type of the keys of the MultiKeyMap
     * @param            the type of the values of the MultiKeyMap
     * @return the MultiKeyMap producing collector
     */
    public static , V> Collector> toMultiKeyMap(
            final Function keyMapper,
            final Function valueMapper, final BinaryOperator mergeFunction) {
        return java.util.stream.Collectors.toMap(keyMapper, valueMapper, mergeFunction,
                MultiKeyMaps::newMultiKeyMap);
    }

    /**
     * Gets a MultiKeyMap producing collector.
     *
     * @param keyMapper           the key producing mapping function
     * @param valueMapper         the value producing mapping function
     * @param mergeFunction       the value merging function
     * @param multiKeyMapSupplier the MultiKeyMap supplier
     * @param                  the type of the stream elements
     * @param                  the type of the sub-keys of the MultiKeyMap
     * @param                  the type of the keys of the MultiKeyMap
     * @param                  the type of the values of the MultiKeyMap
     * @return the MultiKeyMap producing collector
     */
    public static , V, M extends MultiKeyMap> Collector toMultiKeyMap(
            final Function keyMapper,
            final Function valueMapper, final BinaryOperator mergeFunction,
            final Supplier multiKeyMapSupplier) {
        return java.util.stream.Collectors.toMap(keyMapper, valueMapper, mergeFunction,
                multiKeyMapSupplier);
    }

    static final class ConcurrentSetIntersecting implements Collector, Set, Set> {
        private final Supplier> smallestSetSupplier;

        ConcurrentSetIntersecting(final Supplier> smallestSetSupplier) {
            super();
            this.smallestSetSupplier = Objects.requireNonNull(smallestSetSupplier);
        }

        @Override
        public Supplier> supplier() {
            return () -> {
                final Set smallestSet = smallestSetSupplier.get();
                final KeySetView newKeySet = ConcurrentHashMap.newKeySet(smallestSet.size());
                newKeySet.addAll(smallestSet);
                return newKeySet;
            };
        }

        @Override
        public BiConsumer, Set> accumulator() {
            return (set1, set2) -> {
                if ((set2 != smallestSetSupplier.get()) && !set1.isEmpty() && !set2.isEmpty()) {
                    set1.retainAll(set2);
                }
            };
        }

        @Override
        public BinaryOperator> combiner() {
            return (set1, set2) -> set1.isEmpty() || set2.isEmpty()
                    || (set1.retainAll(set2) && set1.isEmpty()) ? Collections.emptySet() : set1;
        }

        @Override
        public Set characteristics() {
            return Collections.unmodifiableSet(EnumSet.allOf(Characteristics.class));
        }

        @Override
        public Function, Set> finisher() {
            return Function.identity();
        }
    }

    static final class SequentialSetIntersecting implements Collector, Set, Set> {
        private final Supplier> smallestSetSupplier;

        SequentialSetIntersecting(final Supplier> smallestSetSupplier) {
            super();
            this.smallestSetSupplier = Objects.requireNonNull(smallestSetSupplier);
        }

        @Override
        public Supplier> supplier() {
            return () -> new HashSet<>(smallestSetSupplier.get());
        }

        @Override
        public BiConsumer, Set> accumulator() {
            return (set1, set2) -> {
                if ((set2 != smallestSetSupplier.get()) && !set1.isEmpty() && !set2.isEmpty()) {
                    set1.retainAll(set2);
                }
            };
        }

        @Override
        public BinaryOperator> combiner() {
            return (set1, set2) -> set1.isEmpty() || set2.isEmpty()
                    || (set1.retainAll(set2) && set1.isEmpty()) ? Collections.emptySet() : set1;
        }

        @Override
        public Set characteristics() {
            return Collections
                    .unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.UNORDERED));
        }

        @Override
        public Function, Set> finisher() {
            return Function.identity();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy