Releases: goldmansachs/gs-collections
4.0.0 (September 2013)
Binaries
Javadoc
JDiff
API differences between 3.0.0 and 4.0.0
New Functionality
Version 4.0 completes the immutable primitive collections work started in 3.2.0. All the primitive collections now have immutable counterparts.
Immutable Primitive Collections
4.0 has immutable primitive sets, stacks, bags and maps. They can each be created using either the toImmutable()
method or the primitive collection factories.
MutableIntSet set = IntHashSet.newSetWith(1, 2, 3);
ImmutableIntSet immutableSet = set.toImmutable();
ImmutableIntSet immutableSet2 = IntSets.immutable.of(1, 2, 3);
MutableIntStack stack = IntArrayStack.newStackWith(1, 2, 3);
ImmutableIntStack immutableStack = stack.toImmutable();
ImmutableIntStack immutableStack2 = IntStacks.immutable.of(1, 2, 3);
MutableIntBag bag = IntHashBag.newBagWith(1, 2, 3);
ImmutableIntBag immutableBag = bag.toImmutable();
ImmutableIntBag immutableBag2 = IntBags.immutable.of(1, 2, 3);
MutableIntCharMap map = IntCharHashMap.newWithKeysValues(1, 'a', 2, 'b', 3, 'c');
ImmutableIntCharMap immutableMap = map.toImmutable();
ImmutableIntCharMap immutableMap2 = IntCharMaps.immutable.ofAll(map);
collect() Methods on RichIterable
The collect() methods on RichIterable are similar to collect(), but they take a primitive function and return a primitive collection. There are variants for all eight primitives.
- collectBoolean()
- collectByte()
- collectChar()
- collectShort()
- collectInt()
- collectFloat()
- collectLong()
- collectDouble()
More API on Primitive Maps
Primitive maps now include additional API from the non-primitive variants.
putAll()
Copies all of the mappings from the map parameter to this map.
keySet()
Returns a set view of the keys contained in the map. Changes to the map are reflected in the set, and vice versa. The set supports removal, which removes the corresponding mapping from the map. It does not support add() or addAll().
values()
Returns a collection view of the values contained in the map. Changes to the map are reflected in the collection, and vice versa. The collection supports removal, which removes the corresponding mapping from the map. It does not support add() or addAll().
3.2.0 (June 2013)
Binaries
Javadoc
New Functionality
Immutable Primitive Containers
Version 3.2 adds initial support for immutable primitive containers, starting with lists. They can be created using the toImmutable()
method or using the primitive list factories.
MutableIntList mutableIntList = IntArrayList.newListWith(1, 2, 3);
ImmutableIntList immutableIntList = mutableIntList.toImmutable();
ImmutableIntList immutableIntList = IntLists.immutable.of(1, 2, 3);
injectInto() on primitive collections
Primitive containers now support injectInto()
. It works like RichIterable.injectInto()
, but it takes an Object<Primitive>ToObjectFunction
, where the primitive type matches the container type. The accumulator can be any type.
IntArrayList arrayList = IntArrayList.newListWith(1, 2, 3);
MutableInteger sum = arrayList.injectInto(new MutableInteger(0), new ObjectIntToObjectFunction<MutableInteger, MutableInteger>()
{
public MutableInteger valueOf(MutableInteger object, int value)
{
return object.add(value);
}
});
Assert.assertEquals(new MutableInteger(6), sum);
injectIntoWithIndex() on primitive lists
injectIntoWithIndex()
is similar to injectInto()
, except the function takes a third parameter: the index of the current element. It's only available on lists since sets and bags don't have indexed positions.
IntArrayList list1 = IntArrayList.newListWith(1, 2, 3);
final IntArrayList list2 = IntArrayList.newListWith(1, 2, 3);
MutableInteger dotProduct = list1.injectIntoWithIndex(new MutableInteger(0), new ObjectIntIntToObjectFunction<MutableInteger, MutableInteger>()
{
public MutableInteger valueOf(MutableInteger object, int value, int index)
{
return object.add(value * list2.get(index));
}
});
Assert.assertEquals(new MutableInteger(14), dotProduct);
dotProduct() on primitive lists
The primitive lists that hold numeric types now support dotProduct()
. It takes a second list, multiplies the pairs of elements appearing at the same index, and sums the products.
@Test
public void dotProduct()
{
IntArrayList list1 = IntArrayList.newListWith(1, 2);
IntArrayList list2 = IntArrayList.newListWith(3, 4);
Assert.assertEquals(1 * 3 + 2 * 4, list1.dotProduct(list2));
}
forEachWithIndex() on primitive lists
Evaluates the <Primitive>IntProcedure
for each element in the list passing in the element and its index.
final StringBuilder string = new StringBuilder();
CharArrayList.newListWith('a', 'b', 'c').forEachWithIndex(new CharIntProcedure()
{
public void value(char each, int index)
{
string.append(each).append('-').append(index);
}
});
Assert.assertEquals("a-1b-2c-3", string.toString());
Unmodifiable wrappers for primitive maps
Version 3.2 adds unmodifiable wrappers for primitive-to-Object maps and Object-to-primitive maps. With this addition, all primitive collections support asUnmodifiable()
.
@Test(expected = UnsupportedOperationException.class)
public void put_throws()
{
MutableIntObjectMap<String> map = IntObjectHashMap.newWithKeysValues(0, "zero", 31, "thirtyOne", 32, "thirtyTwo");
MutableIntObjectMap<String> unmodifiableMap = map.asUnmodifiable();
unmodifiableMap.put(0, "one");
}
No API changes in this release
This release preserves binary, source, and serialization compatibility with 3.0.
- The method toImmutable() already existed on primitive collections in GS Collections 3.0, but it threw UnsupportedOperationException.
- injectInto(), injectIntoWithIndex() and forEachWithIndex() only have been added to the implementations. They will be added to the primitive iterable interfaces in the next major release.
3.1.0 (May 2013)
Binaries
Javadoc
New Functionality
Synchronized and Unmodifiable Wrappers for Primitive Collections
The wrappers for primitive collections are similar to the wrappers for generic collections.
asUnmodifiable()
returns a wrapper which throws on mutating methods. All other methods pass through to the wrapped collection.
For example:
@Test(expected = UnsupportedOperationException.class)
public void add_throws()
{
IntArrayList list = IntArrayList.newListWith(1, 2, 3);
MutableIntList unmodifiableList = list.asUnmodifiable();
unmodifiableList.add(4);
}
asSynchronized()
returns a wrapper which synchronizes on a lock before delegating to the wrapped collection. The iterator is still not synchronized, so the warning on [java.util.Collections.synchronizedCollection()](http://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#synchronizedCollection(java.util.Collection\)) applies to this API as well.
By default, the lock is the wrapper itself. Use the wrapper's constructor instead of the asSynchronized() method if you need to customize the lock.
Fork Join Functionality
This release includes a new module, gs-collections-forkjoin-3.1.0.jar
. Its main utility is the FJIterate
class. It's conceptually similar to ParallelIterate
, but it's based on Java 7's ForkJoinPool
s. All other modules continue to be backward compatible with Java 5.
Improved memory benchmarks and performance tests
This release includes additional performance test for UnifiedSet
and ImmutableArrayList
. Existing performance tests are now more fair. The memory tests record more data points.
No API changes in this release
The methods asUnmodifiable()
and asSynchronized()
already existed on primitive collections in GS Collections 3.0, but they threw UnsupportedOperationException
. There are no API changes in this release. It preserves binary, source, and serialization compatibility with 3.0.
3.0.1 (March 2013)
Binaries
Javadoc
JDiff
API differences between 2.0.0 and 3.0.0
New Functionality
Primitive Collections
GS Collections 3.0 has memory optimized collections for primitive types. The collections include lists, sets, stacks, maps and bags. The interface hierarchy is very similar to the hierarchy of object collections.
Some examples:
Interface | Analogous to |
---|---|
IntIterable | RichIterable |
MutableIntCollection | MutableCollection |
IntList | ListIterable |
MutableIntList | MutableList |
The hierarchy is similar for the other primitive types.
Primitive Lists
Primitive lists are backed by an array (like FastList), but it's a primitive array instead of Object[]. They are named IntArrayList, FloatArrayList, etc.
BooleanArrayList is a special case. Current JVMs use one byte per boolean in a boolean[], instead of one bit per boolean. BooleanArrayLists are backed by a java.util.BitSet as an optimization.
To create an IntArrayList, use one of the following:
IntArrayList emptyList = new IntArrayList();
IntArrayList intList = IntArrayList.newListWith(1, 2, 3);
IntArrayList listFromIntIterable = IntArrayList.newListWith(IntHashSet.newSetWith(1, 2, 3));
IntInterval
IntInterval is Similar to Interval, but implements the IntList interface instead of List. It represents a range of ints and a step value.
Assert.assertEquals(IntArrayList.newListWith(1, 2, 3), IntInterval.oneTo(3));
Assert.assertEquals(IntArrayList.newListWith(1, 3, 5), IntInterval.oneToBy(5, 2));
Primitive Sets
The primitive set implementations are backed by hashtables. The hashtables are implemented using open addressing and quadratic probing. They are named IntHashSet, FloatHashSet, etc. BooleanHashSet is implemented using a single integer to keep track of the four states [], [F], [T], or [T, F].
Primitive Stacks
Primitive stacks are similar to ArrayStack but backed by primitive lists instead of FastList.
Primitive Bags
Primitive bags are similar to HashBag, but both item and count are primitives.
Primitive Maps
There are three types of primitive maps:
- Object to Primitive (ObjectIntHashMap, ObjectFloatHashMap, etc.)
- Primitive to Object (IntObjectHashMap, FloatObjectHashMap, etc.)
- Primitive to Primitive (IntIntHashMap, IntLongHashMap, etc.)
There are no maps with boolean keys. All the maps are implemented as hashtables using open addressing and quadratic probing.
get()
Since there is no concept of null when working with primitives, maps with primitive values return an EMPTY_VALUE sentinel from get() if the key is not present in the map. EMPTY_VALUE is false for boolean and 0 for all other primitives.
IntIntHashMap map = IntIntHashMap.newWithKeysValues(1, 1, 2, 2, 3, 3);
Assert.assertEquals(1, map.get(1));
Assert.assertEquals(2, map.get(2));
Assert.assertEquals(3, map.get(3));
Assert.assertEquals(0, this.map.get(4));
Assert.assertFalse(new IntBooleanHashMap().get(1));
getOrThrow()
If the key is present in the map, getOrThrow() returns the corresponding value, otherwise it throws IllegalStateException.
final IntIntHashMap map = IntIntHashMap.newWithKeysValues(1, 1, 2, 2, 3, 3);
Assert.assertEquals(1, map.getOrThrow(1));
Assert.assertEquals(2, map.getOrThrow(2));
Assert.assertEquals(3, map.getOrThrow(3));
Verify.assertThrows(IllegalStateException.class, new Runnable()
{
public void run()
{
map.getOrThrow(4);
}
});
Primitive Code Blocks and Code Block Factories
IntPredicates, LongPredicates, etc. can be used to create common instances of IntPredicate, LongPredicate, etc.
More API
RichIterable.aggregateBy()
Groups the elements in the RichIterable by the function named groupBy. Then all the values that map to the same key are aggregated together using the Function2 named aggregator. aggregateBy is conceptually similar to a groupBy into a Multimap followed by injectInto on each collection of values, finally yielding a MapIterable. The third parameter, a Function0 named factory, creates the initial value for each aggregation (the accumulator used by injectInto).
Aggregate values are allowed to be immutable since they are replaced in the map.
Function0<Integer> factory = new Function0<Integer>()
{
public Integer value()
{
return Integer.valueOf(0);
}
};
Function2<Integer, Integer, Integer> sumAggregator = new Function2<Integer, Integer, Integer>()
{
public Integer value(Integer aggregate, Integer value)
{
return aggregate + value;
}
};
Function<Integer, Integer> groupBy = new Function<Integer, Integer>()
{
public Integer valueOf(Integer integer)
{
return integer % 2;
}
};
FastList<Integer> integers = FastList.newListWith(1, 1, 1, 2, 2, 3);
MutableMap<Integer, Integer> aggregation = integers.aggregateBy(groupBy, factory, sumAggregator);
Assert.assertEquals(4, aggregation.get(0).intValue());
Assert.assertEquals(6, aggregation.get(1).intValue());
RichIterable.aggregateInPlaceBy()
Conceptually similar to aggregateBy, but aggregateInPlaceBy mutates values in the result map instead of replacing them. The aggregator parameter is a Procedure2 instead of a Function2.
Function0<AtomicInteger> factory = new Function0<AtomicInteger>()
{
public AtomicInteger value()
{
return new AtomicInteger(0);
}
};
Procedure2<AtomicInteger, Integer> sumAggregator = new Procedure2<AtomicInteger, Integer>()
{
public void value(AtomicInteger aggregate, Integer value)
{
aggregate.addAndGet(value);
}
};
Function<Integer, Integer> groupBy = new Function<Integer, Integer>()
{
public Integer valueOf(Integer integer)
{
return integer % 2;
}
};
FastList<Integer> integers = FastList.newListWith(1, 1, 1, 2, 2, 3);
MutableMap<Integer, AtomicInteger> aggregation = integers.aggregateInPlaceBy(groupBy, factory, sumAggregator);
Assert.assertEquals(4, aggregation.get(0).intValue());
Assert.assertEquals(6, aggregation.get(1).intValue());
RichIterable.noneSatisfy()
Returns true if the predicate evaluates to false for every element of the iterable or if the iterable is empty, otherwise it returns false. It's conceptually similar to RichIterable.allSatisfy(Predicates.not(predicate)).
Assert.assertFalse(IntArrayList.newListWith(1, 2, 3).noneSatisfy(IntPredicates.greaterThan(0)));
Assert.assertTrue(IntArrayList.newListWith(1, 2, 3).noneSatisfy(IntPredicates.greaterThan(3)));
ListIterable.distinct()
Returns a new ListIterable containing the distinct elements in the list. It's conceptually similar to toSet().toList() but it retains the original order. If an element appears multiple times in the list, only the first one will be copied into the result.
Verify.assertListsEqual(Lists.mutable.of(5, 2, 3, 4), Lists.mutable.of(5, 2, 3, 5, 4, 2).distinct());
FastList.newWithNValues()
Returns a new FastList containing N values generated by calling the Function0 N times.
Assert.assertEquals(FastList.newListWith(1, 1, 1, 1, 1), FastList.newWithNValues(5, Functions0.value(1)))
Bag.selectByOccurrences()
Returns all elements of the bag that have a number of occurrences that satisfy the predicate.
MutableBag<Integer> integers = Bags.mutable.of(1, 1, 1, 1, 2, 2, 2, 3, 3, 4);
Assert.assertEquals(Bags.mutable.of(1, 1, 1, 1, 3, 3), integers.selectByOccurrences(IntPredicates.isEven()));
Bag.toStringOfItemToCount()
Returns a string representation of the bag. It's conceptually the same as Bag.toMapOfItemToCount().toString() but without creating the intermediate map.
Assert.assertEquals("{}", Bags.mutable.of().toStringOfItemToCount());
Assert.assertEquals("{1=3}", Bags.mutable.of(1, 1, 1).toStringOfItemToCount());
MutableMap.updateValue() and MutableMap.updateValueWith()
Looks up the value associated with the given key, applies the Function to it, and replaces the value. If there is no value associated with the key, starts it off with a value supplied by the factory Function0.
updateValueWith() is the same as updateValue() with a Function2 and an extra parameter which is passed as a second argument to the function.
MutableMap<String, Integer> integers = UnifiedMap.newWithKeysValues("two", 2, "three", 3);
integers.updateValue("two", Functions0.value(1), Functions.squaredInteger());
Assert.assertEquals(UnifiedMap.newWithKeysValues("two", 4, "three", 3), integers);
integers.updateValue("four", Functions0.value(4), Functions.squaredInteger());
Assert.assertEquals(UnifiedMap.newWithKeysValues("two", 4, "three", 3, "four", 16), integers);
2.0.0 (August 2012)
Binaries
Javadoc
JDiff
API differences between 1.2.0 and 2.0.0
New Functionality
Stack Container
A stack is a collection enforcing a last-in, first-out order; its methods iterate over elements beginning with the most-recently added element.
The StackIterable interface extends RichIterable and its methods such as forEach and toString process elements in reverse order of their addition to the stack. MutableStack extends StackIterable and adds mutating methods like push, pop, and clear.
The concrete type is ArrayStack. The current implementation delegates to a FastList and thus has similar runtime complexity. For example, ArrayStack's push method takes amortized constant time, like FastList's add method. In GS Collections, stacks are not lists. This is a deliberate difference from java.util.Stack which extends Vector.
push | Adds a new element to the top of the stack. |
pop | Returns the top (most recently-added) element and removes it from the collection |
pop(int count) | Returns a ListIterable of the number of elements specified by the count, beginning with the top of the stack. |
peek | Returns but does not remove the top element. Note that, on a stack, getFirst likewise returns the top element, and that getLast throws an exception. |
peek(int count) | Returns a ListIterable of the number of elements specified by the count, beginning with the top of the stack; does not remove the elements from the stack. |
peekAt(int index) | Returns the element at index. |
To create a MutableStack, use one of the following static factory methods:
MutableStack<Integer> emptyStack = ArrayStack.newStack();
MutableStack<Integer> mutableStack = ArrayStack.newStackWith(1, 2, 3);
MutableStack<Integer> stackFromFastList = ArrayStack.newStack(FastList.newListWith(1, 2, 3));
MutableStack<Integer> stackFromTopToBottom = ArrayStack.newStackFromTopToBottom(3, 2, 1);
MutableStack<Integer> stackUsingStacksFactory = Stacks.mutable.of(1, 2, 3);
ConcurrentHashMap
GS Collections 2.0 includes a new concurrent hashmap implementation, com.gs.collections.impl.map.mutable.ConcurrentHashMap.
Primitive Iterables and their Lazy Implementations
The primitive iterable interfaces are memory-optimized for primitives.
int | IntIterable |
long | LongIterable |
float | FloatIterable |
double | DoubleIterable |
They are inspired by the RichIterable interface, and contain a subset of the iteration pattern methods from RichIterable like collect. They add some primitive specific API like sum, average, etc. They also include Iterators specialized for each primitive type. They do not extend Iterator, to prevent accidental auto-boxing.
The current implementations use lazy evaluation. Here's an example which calculates the average of a collection of ints.
double average = Interval.oneTo(4).collectInt(PrimitiveFunctions.unboxIntegerToInt()).average();
Here, conllectInt() returns an instance of CollectIntIterable, an implementation of IntIterable. CollectIntIterable transforms a source iterable using an IntFunction as it iterates.
The PrimitiveFunctions class has a number of common unboxing functions. For example PrimitiveFunctions.unboxIntegerToInt() returns an IntFunction.
More RichIterable API
selectInstancesOf()
Returns all elements of the source collection that are instances of the Class parameter.
MutableList<Number> numbers = FastList.newListWith(1.0, 2, 3.0, 4, 5.0);
MutableList<Integer> integers = numbers.selectInstancesOf(Integer.class);
It is meant to address this problem with select and an “instanceOf” Predicate.
MutableList<Number> numbers = FastList.newListWith(1.0, 2, 3.0, 4, 5.0);
MutableList<Number> integers = numbers.select(Predicates.instanceOf(Integer.class));
The result is a collection of Number instead of Integer. If we try to add a simple cast, we get a compiler error.
MutableList<Integer> integers = (MutableList<Integer>) numbers.select(Predicates.instanceOf(Integer.class));
The error message tells us we’re trying to cast to an inconvertible type. We can use raw types, or do a double cast, but neither is intuitive.
MutableList<Integer> integers = (MutableList<Integer>) (MutableList<?>) numbers.select(Predicates.instanceOf(Integer.class));
The new method selectInstancesOf() addresses these problems with types, plus and it’s concise and communicates what you’re doing.
sumOf() Methods - sumOfInt(), sumOfLong(), sumOfFloat(), sumOfDouble()
The sumOf methods return the final primitive result of evaluating function for each element of the iterable and adding elements together. For example, sumOfInt() takes an IntFunction and returns the int sum without auto-boxing.