com.xenoamess.commons.primitive.iterators.IntIteratorSpliterator Maven / Gradle / Ivy
Show all versions of commonx Show documentation
/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.xenoamess.commons.primitive.iterators;
import com.xenoamess.commons.primitive.Primitive;
import com.xenoamess.commons.primitive.collections.IntCollection;
import com.xenoamess.commons.primitive.comparators.IntComparator;
import com.xenoamess.commons.primitive.functions.IntConsumer;
import java.util.Spliterator;
import java.util.function.Consumer;
/**
* An object for traversing and partitioning elements of a source. The source
* of elements covered by a Spliterator could be, for example, an array, a
* {@link java.util.Collection}, an IO channel, or a generator function.
*
* A Spliterator may traverse elements individually ({@link
* #tryAdvance tryAdvance()}) or sequentially in bulk
* ({@link #forEachRemaining forEachRemaining()}).
*
*
A Spliterator may also partition off some of its elements (using
* {@link #trySplit}) as another Spliterator, to be used in
* possibly-parallel operations. Operations using a Spliterator that
* cannot split, or does so in a highly imbalanced or inefficient
* manner, are unlikely to benefit from parallelism. Traversal
* and splitting exhaust elements; each Spliterator is useful for only a single
* bulk computation.
*
*
A Spliterator also reports a set of {@link #characteristics()} of its
* structure, source, and elements from among {@link #ORDERED},
* {@link #DISTINCT}, {@link #SORTED}, {@link #SIZED}, {@link #NONNULL},
* {@link #IMMUTABLE}, {@link #CONCURRENT}, and {@link #SUBSIZED}. These may
* be employed by Spliterator clients to control, specialize or simplify
* computation. For example, a Spliterator for a {@link java.util.Collection} would
* report {@code SIZED}, a Spliterator for a {@link java.util.Set} would report
* {@code DISTINCT}, and a Spliterator for a {@link java.util.SortedSet} would also
* report {@code SORTED}. Characteristics are reported as a simple unioned bit
* set.
*
* Some characteristics additionally constrain method behavior; for example if
* {@code ORDERED}, traversal methods must conform to their documented ordering.
* New characteristics may be defined in the future, so implementors should not
* assign meanings to unlisted values.
*
*
A Spliterator that does not report {@code IMMUTABLE} or
* {@code CONCURRENT} is expected to have a documented policy concerning:
* when the spliterator binds to the element source; and detection of
* structural interference of the element source detected after binding. A
* late-binding Spliterator binds to the source of elements at the
* point of first traversal, first split, or first query for estimated size,
* rather than at the time the Spliterator is created. A Spliterator that is
* not late-binding binds to the source of elements at the point of
* construction or first invocation of any method. Modifications made to the
* source prior to binding are reflected when the Spliterator is traversed.
* After binding a Spliterator should, on a best-effort basis, throw
* {@link java.util.ConcurrentModificationException} if structural interference is
* detected. Spliterators that do this are called fail-fast. The
* bulk traversal method ({@link #forEachRemaining forEachRemaining()}) of a
* Spliterator may optimize traversal and check for structural interference
* after all elements have been traversed, rather than checking per-element and
* failing immediately.
*
*
Spliterators can provide an estimate of the number of remaining elements
* via the {@link #estimateSize} method. Ideally, as reflected in characteristic
* {@link #SIZED}, this value corresponds exactly to the number of elements
* that would be encountered in a successful traversal. However, even when not
* exactly known, an estimated value may still be useful to operations
* being performed on the source, such as helping to determine whether it is
* preferable to split further or traverse the remaining elements sequentially.
*
*
Despite their obvious utility in parallel algorithms, spliterators are not
* expected to be thread-safe; instead, implementations of parallel algorithms
* using spliterators should ensure that the spliterator is only used by one
* thread at a time. This is generally easy to attain via serial
* thread-confinement, which often is a natural consequence of typical
* parallel algorithms that work by recursive decomposition. A thread calling
* {@link #trySplit()} may hand over the returned Spliterator to another thread,
* which in turn may traverse or further split that Spliterator. The behaviour
* of splitting and traversal is undefined if two or more threads operate
* concurrently on the same spliterator. If the original thread hands a
* spliterator off to another thread for processing, it is best if that handoff
* occurs before any elements are consumed with {@link #tryAdvance(Consumer)
* tryAdvance()}, as certain guarantees (such as the accuracy of
* {@link #estimateSize()} for {@code SIZED} spliterators) are only valid before
* traversal has begun.
*
* The subtype default implementations of
* {@link java.util.Spliterator#tryAdvance(java.util.function.Consumer)}
* and {@link java.util.Spliterator#forEachRemaining(java.util.function.Consumer)} box
* primitive values to instances of their corresponding wrapper class. Such
* boxing may undermine any performance advantages gained by using the primitive
* specializations. To avoid boxing, the corresponding primitive-based methods
* should be used. For example,
* {@link java.util.Spliterator.OfInt#tryAdvance(java.util.function.IntConsumer)}
* and {@link java.util.Spliterator.OfInt#forEachRemaining(java.util.function.IntConsumer)}
* should be used in preference to
* {@link java.util.Spliterator.OfInt#tryAdvance(java.util.function.Consumer)} and
* {@link java.util.Spliterator.OfInt#forEachRemaining(java.util.function.Consumer)}.
* Traversal of primitive values using boxing-based methods
* {@link #tryAdvance tryAdvance()} and
* {@link #forEachRemaining(java.util.function.Consumer) forEachRemaining()}
* does not affect the order in which the values, transformed to boxed values,
* are encountered.
*
* @author XenoAmess
* @version 0.8.0
* @apiNote
Spliterators, like {@code Iterator}s, are for traversing the elements of
* a source. The {@code Spliterator} API was designed to support efficient
* parallel traversal in addition to sequential traversal, by supporting
* decomposition as well as single-element iteration. In addition, the
* protocol for accessing elements via a Spliterator is designed to impose
* smaller per-element overhead than {@code Iterator}, and to avoid the inherent
* race involved in having separate methods for {@code hasNext()} and
* {@code next()}.
*
*
For mutable sources, arbitrary and non-deterministic behavior may occur if
* the source is structurally interfered with (elements added, replaced, or
* removed) between the time that the Spliterator binds to its data source and
* the end of traversal. For example, such interference will produce arbitrary,
* non-deterministic results when using the {@code java.util.stream} framework.
*
*
Structural interference of a source can be managed in the following ways
* (in approximate order of decreasing desirability):
*
* - The source cannot be structurally interfered with.
*
For example, an instance of
* {@link java.util.concurrent.CopyOnWriteArrayList} is an immutable source.
* A Spliterator created from the source reports a characteristic of
* {@code IMMUTABLE}.
* - The source manages concurrent modifications.
*
For example, a key set of a {@link java.util.concurrent.ConcurrentHashMap}
* is a concurrent source. A Spliterator created from the source reports a
* characteristic of {@code CONCURRENT}.
* - The mutable source provides a late-binding and fail-fast Spliterator.
*
Late binding narrows the window during which interference can affect
* the calculation; fail-fast detects, on a best-effort basis, that structural
* interference has occurred after traversal has commenced and throws
* {@link java.util.ConcurrentModificationException}. For example, {@link java.util.ArrayList},
* and many other non-concurrent {@code Collection} classes in the JDK, provide
* a late-binding, fail-fast spliterator.
* - The mutable source provides a non-late-binding but fail-fast Spliterator.
*
The source increases the likelihood of throwing
* {@code ConcurrentModificationException} since the window of potential
* interference is larger.
* - The mutable source provides a late-binding and non-fail-fast Spliterator.
*
The source risks arbitrary, non-deterministic behavior after traversal
* has commenced since interference is not detected.
*
* - The mutable source provides a non-late-binding and non-fail-fast
* Spliterator.
*
The source increases the risk of arbitrary, non-deterministic behavior
* since non-detected interference may occur after construction.
*
*
*
* Example. Here is a class (not a very useful one, except
* for illustration) that maintains an array in which the actual data
* are held in even locations, and unrelated tag data are held in odd
* locations. Its Spliterator ignores the tags.
*
*
{@code
* class TaggedArray {
* private final Object[] elements; // immutable after construction
* TaggedArray(T[] data, Object[] tags) {
* int size = data.length;
* if (tags.length != size) throw new IllegalArgumentException();
* this.elements = new Object[2 * size];
* for (int i = 0, j = 0; i < size; ++i) {
* elements[j++] = data[i];
* elements[j++] = tags[i];
* }
* }
*
* public Spliterator spliterator() {
* return new TaggedArraySpliterator<>(elements, 0, elements.length);
* }
*
* static class TaggedArraySpliterator implements Spliterator {
* private final Object[] array;
* private int origin; // current index, advanced on split or traversal
* private final int fence; // one past the greatest index
*
* TaggedArraySpliterator(Object[] array, int origin, int fence) {
* this.array = array; this.origin = origin; this.fence = fence;
* }
*
* public void forEachRemaining(Consumer super T> action) {
* for (; origin < fence; origin += 2)
* action.accept((T) array[origin]);
* }
*
* public boolean tryAdvance(Consumer super T> action) {
* if (origin < fence) {
* action.accept((T) array[origin]);
* origin += 2;
* return true;
* }
* else // cannot advance
* return false;
* }
*
* public Spliterator trySplit() {
* int lo = origin; // divide range in half
* int mid = ((lo + fence) >>> 1) & ~1; // force midpoint to be even
* if (lo < mid) { // split out left half
* origin = mid; // reset this Spliterator's origin
* return new TaggedArraySpliterator<>(array, lo, mid);
* }
* else // too small to split
* return null;
* }
*
* public long estimateSize() {
* return (long)((fence - origin) / 2);
* }
*
* public int characteristics() {
* return ORDERED | SIZED | IMMUTABLE | SUBSIZED;
* }
* }
* }}
*
* As an example how a parallel computation framework, such as the
* {@code java.util.stream} package, would use Spliterator in a parallel
* computation, here is one way to implement an associated parallel forEach,
* that illustrates the primary usage idiom of splitting off subtasks until
* the estimated amount of work is small enough to perform
* sequentially. Here we assume that the order of processing across
* subtasks doesn't matter; different (forked) tasks may further split
* and process elements concurrently in undetermined order. This
* example uses a {@link java.util.concurrent.CountedCompleter};
* similar usages apply to other parallel task constructions.
*
*
{@code
* static void parEach(TaggedArray a, Consumer action) {
* Spliterator s = a.spliterator();
* long targetBatchSize = s.estimateSize() / (ForkJoinPool.getCommonPoolParallelism() * 8);
* new ParEach(null, s, action, targetBatchSize).invoke();
* }
*
* static class ParEach extends CountedCompleter {
* final Spliterator spliterator;
* final Consumer action;
* final long targetBatchSize;
*
* ParEach(ParEach parent, Spliterator spliterator,
* Consumer action, long targetBatchSize) {
* super(parent);
* this.spliterator = spliterator; this.action = action;
* this.targetBatchSize = targetBatchSize;
* }
*
* public void compute() {
* Spliterator sub;
* while (spliterator.estimateSize() > targetBatchSize &&
* (sub = spliterator.trySplit()) != null) {
* addToPendingCount(1);
* new ParEach<>(this, sub, action, targetBatchSize).fork();
* }
* spliterator.forEachRemaining(action);
* propagateCompletion();
* }
* }}
* @implNote If the boolean system property {@code org.openjdk.java.util.stream.tripwire}
* is set to {@code true} then diagnostic warnings are reported if boxing of
* primitive values occur when operating on primitive subtype specializations.
* @see java.util.Collection
* @see IntSpliterator
* @since 1.8
*/
public class IntIteratorSpliterator implements IntSpliterator.IntOfInteger, Primitive {
static final int BATCH_UNIT = 1 << 10; // batch array size increment
static final int MAX_BATCH = 1 << 25; // max batch array size;
private final IntCollection collection; // null OK
private IntIterator it;
private final int characteristics;
private long est; // size estimate
private int batch; // batch size for splits
/**
* Creates a spliterator using the given
* collection's {@link java.util.Collection#iterator()} for traversal,
* and reporting its {@link java.util.Collection#size()} as its initial
* size.
*
* @param characteristics properties of this spliterator's
* source or elements.
* @param collection a {@link com.xenoamess.commons.primitive.collections.IntCollection} object.
*/
public IntIteratorSpliterator(IntCollection collection, int characteristics) {
this.collection = collection;
this.it = null;
this.characteristics = (characteristics & Spliterator.CONCURRENT) == 0
? characteristics | Spliterator.SIZED | Spliterator.SUBSIZED
: characteristics;
}
/**
* Creates a spliterator using the given iterator
* for traversal, and reporting the given initial size
* and characteristics.
*
* @param iterator the iterator for the source
* @param size the number of elements in the source
* @param characteristics properties of this spliterator's
* source or elements.
*/
public IntIteratorSpliterator(IntIterator iterator, long size, int characteristics) {
this.collection = null;
this.it = iterator;
this.est = size;
this.characteristics = (characteristics & IntSpliterator.CONCURRENT) == 0
? characteristics | IntSpliterator.SIZED | IntSpliterator.SUBSIZED
: characteristics;
}
/**
* Creates a spliterator using the given iterator for a
* source of unknown size, reporting the given
* characteristics.
*
* @param iterator the iterator for the source
* @param characteristics properties of this spliterator's
* source or elements.
*/
public IntIteratorSpliterator(IntIterator iterator, int characteristics) {
this.collection = null;
this.it = iterator;
this.est = Long.MAX_VALUE;
this.characteristics = characteristics & ~(Spliterator.SIZED | IntSpliterator.SUBSIZED);
}
/**
* {@inheritDoc}
*/
@Override
public IntOfInteger trySplit() {
/*
* Split into arrays of arithmetically increasing batch
* sizes. This will only improve parallel performance if
* per-element Consumer actions are more costly than
* transferring them into an array. The use of an
* arithmetic progression in split sizes provides overhead
* vs parallelism bounds that do not particularly favor or
* penalize cases of lightweight vs heavyweight element
* operations, across combinations of #elements vs #cores,
* whether or not either are known. We generate
* O(sqrt(#elements)) splits, allowing O(sqrt(#cores))
* potential speedup.
*/
IntIterator i;
long s;
if ((i = it) == null) {
assert collection != null;
i = it = collection.iterator();
s = est = (long) collection.size();
} else {
s = est;
}
if (s > 1 && i.hasNext()) {
int n = batch + BATCH_UNIT;
if (n > s) {
n = (int) s;
}
if (n > MAX_BATCH) {
n = MAX_BATCH;
}
int[] a = new int[n];
int j = 0;
do {
a[j] = i.nextPrimitive();
} while (++j < n && i.hasNext());
batch = j;
if (est != Long.MAX_VALUE) {
est -= j;
}
return new IntArraySpliterator(a, 0, j, characteristics);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void forEachRemaining(IntConsumer action) {
if (action == null) {
throw new NullPointerException();
}
it.forEachRemaining(action);
}
/**
* {@inheritDoc}
*/
@Override
public boolean tryAdvance(IntConsumer action) {
if (action == null) {
throw new NullPointerException();
}
if (it.hasNext()) {
action.accept(it.nextPrimitive());
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public long estimateSize() {
return est;
}
/**
* {@inheritDoc}
*/
@Override
public int characteristics() {
return characteristics;
}
/**
* {@inheritDoc}
*/
@Override
public IntComparator getComparator() {
if (hasCharacteristics(Spliterator.SORTED)) {
return null;
}
throw new IllegalStateException();
}
}