Skip to content

Commit d5742ed

Browse files
committed
[collect] Add method to get i-th element of Collections
This change adds a new method `Collections2.getElement(Collection, int)`, which returns the i-th element of the collection. Unlike existing methods via Java `Stream` or `Iterable`, the method in this change supports fast access for `ImmutableCollection`s backed by an internal array in `O(1)` instead of `O(n)` with the existing approach. Users can use this method to, e.g., retrieve a random element from `ImmutableSet` (e.g., to select a random node to connect to from a pool of nodes in a distributed system).
1 parent 0fad76f commit d5742ed

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

android/guava/src/com/google/common/collect/Collections2.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,4 +693,32 @@ private static boolean isPermutation(List<?> first, List<?> second) {
693693
}
694694
return map;
695695
}
696+
697+
/**
698+
* Returns the element in the provided {@link Collection} at position {@code index}.
699+
*
700+
* @param collection The {@link Collection} to get the element from
701+
* @param index The index of the element to get.
702+
* @return The element.
703+
*
704+
* @throws IndexOutOfBoundsException if {@code index} is not within range of {@code 0} (inclusive)
705+
* and {@code collection.size()} (exclusive).
706+
*/
707+
public static <T extends @Nullable Object> T getElement(Collection<T> collection, int index) {
708+
int size = collection.size();
709+
if (size < index) {
710+
throw new IndexOutOfBoundsException();
711+
}
712+
713+
// Fast path: `collection` is an `ImmutableCollection` and backed by an array of elements.
714+
if (collection instanceof ImmutableCollection) {
715+
ImmutableCollection<T> immutableCollection = (ImmutableCollection<T>) collection;
716+
Object[] internalArray = immutableCollection.internalArray();
717+
if (internalArray != null) {
718+
return (T) internalArray[immutableCollection.internalArrayStart() + index];
719+
}
720+
}
721+
722+
return Iterables.getFirst(Iterables.skip(collection, index), null);
723+
}
696724
}

guava-tests/test/com/google/common/collect/Collections2Test.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.google.common.collect.Lists.newArrayList;
2222
import static com.google.common.truth.Truth.assertThat;
2323
import static java.util.Collections.nCopies;
24+
import static org.junit.Assert.assertThrows;
2425

2526
import com.google.common.annotations.GwtCompatible;
2627
import com.google.common.annotations.GwtIncompatible;
@@ -498,4 +499,58 @@ public void testToStringImplWithNullEntries() throws Exception {
498499

499500
assertEquals(list.toString(), Collections2.toStringImpl(list));
500501
}
502+
503+
public void testGetElementOutOfRangeWithSet() {
504+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Sets.newHashSet(), -1));
505+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Sets.newHashSet(), 0));
506+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Sets.newHashSet(), 1));
507+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Sets.newHashSet("a"), -1));
508+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Sets.newHashSet("a"), 1));
509+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Sets.newHashSet("a"), -1));
510+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Sets.newHashSet("a", "b"), 2));
511+
}
512+
513+
public void testGetElementOutOfRangeWithList() {
514+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Lists.newArrayList(), -1));
515+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Lists.newArrayList(), 0));
516+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Lists.newArrayList(), 1));
517+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Lists.newArrayList("a"), -1));
518+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Lists.newArrayList("a"), 1));
519+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Lists.newArrayList("a"), -1));
520+
assertThrows(IndexOutOfBoundsException.class, () -> Collections2.getElement(Lists.newArrayList("a", "b"), 2));
521+
}
522+
523+
public void testGetElementFromSet() {
524+
assertThat(Collections2.getElement(Sets.newHashSet("a"), 0)).isEqualTo("a");
525+
assertThat(Collections2.getElement(Sets.newHashSet("b"), 0)).isEqualTo("b");
526+
assertThat(Collections2.getElement(Sets.newHashSet("a", "b"), 0)).isEqualTo("a");
527+
assertThat(Collections2.getElement(Sets.newHashSet("a", "b"), 1)).isEqualTo("b");
528+
assertThat(Collections2.getElement(Sets.newHashSet("b", "a"), 0)).isEqualTo("a");
529+
assertThat(Collections2.getElement(Sets.newHashSet("b", "a"), 1)).isEqualTo("b");
530+
}
531+
532+
public void testGetElementFromList() {
533+
assertThat(Collections2.getElement(Lists.newArrayList("a"), 0)).isEqualTo("a");
534+
assertThat(Collections2.getElement(Lists.newArrayList("a", "b"), 0)).isEqualTo("a");
535+
assertThat(Collections2.getElement(Lists.newArrayList("a", "b"), 1)).isEqualTo("b");
536+
assertThat(Collections2.getElement(Lists.newArrayList("b", "a"), 0)).isEqualTo("b");
537+
assertThat(Collections2.getElement(Lists.newArrayList("b", "a"), 1)).isEqualTo("a");
538+
}
539+
540+
public void testGetElementFromImmutableSet() {
541+
assertThat(Collections2.getElement(ImmutableSet.of("a"), 0)).isEqualTo("a");
542+
assertThat(Collections2.getElement(ImmutableSet.of("b"), 0)).isEqualTo("b");
543+
assertThat(Collections2.getElement(ImmutableSet.of("a", "b"), 0)).isEqualTo("a");
544+
assertThat(Collections2.getElement(ImmutableSet.of("a", "b"), 1)).isEqualTo("b");
545+
assertThat(Collections2.getElement(ImmutableSet.of("b", "a"), 0)).isEqualTo("a");
546+
assertThat(Collections2.getElement(ImmutableSet.of("b", "a"), 1)).isEqualTo("b");
547+
}
548+
549+
public void testGetElementFromImmutableList() {
550+
assertThat(Collections2.getElement(ImmutableList.of("a"), 0)).isEqualTo("a");
551+
assertThat(Collections2.getElement(ImmutableList.of("a", "b"), 0)).isEqualTo("a");
552+
assertThat(Collections2.getElement(ImmutableList.of("a", "b"), 1)).isEqualTo("b");
553+
assertThat(Collections2.getElement(ImmutableList.of("b", "a"), 0)).isEqualTo("b");
554+
assertThat(Collections2.getElement(ImmutableList.of("b", "a"), 1)).isEqualTo("a");
555+
}
501556
}

guava/src/com/google/common/collect/Collections2.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,4 +699,32 @@ private static boolean isPermutation(List<?> first, List<?> second) {
699699
Multiset<?> secondMultiset = HashMultiset.create(second);
700700
return firstMultiset.equals(secondMultiset);
701701
}
702+
703+
/**
704+
* Returns the element in the provided {@link Collection} at position {@code index}.
705+
*
706+
* @param collection The {@link Collection} to get the element from
707+
* @param index The index of the element to get.
708+
* @return The element.
709+
*
710+
* @throws IndexOutOfBoundsException if {@code index} is not within range of {@code 0} (inclusive)
711+
* and {@code collection.size()} (exclusive).
712+
*/
713+
public static <T extends @Nullable Object> T getElement(Collection<T> collection, int index) {
714+
int size = collection.size();
715+
if (size < index) {
716+
throw new IndexOutOfBoundsException();
717+
}
718+
719+
// Fast path: `collection` is an `ImmutableCollection` and backed by an array of elements.
720+
if (collection instanceof ImmutableCollection) {
721+
ImmutableCollection<T> immutableCollection = (ImmutableCollection<T>) collection;
722+
Object[] internalArray = immutableCollection.internalArray();
723+
if (internalArray != null) {
724+
return (T) internalArray[immutableCollection.internalArrayStart() + index];
725+
}
726+
}
727+
728+
return Iterables.getFirst(Iterables.skip(collection, index), null);
729+
}
702730
}

0 commit comments

Comments
 (0)