Merge "Add useful utils to CollectionUtils"

This commit is contained in:
Jean Chalard 2022-11-15 08:50:19 +00:00 • committed by Gerrit Code Review
commit 55de7e4c84
2 changed files with 148 additions and 0 deletions

View file

@ -18,12 +18,15 @@ package com.android.net.module.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.SparseArray;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
/**
@ -291,4 +294,100 @@ public final class CollectionUtils {
@NonNull final Predicate<? super T> condition) {
return -1 != indexOf(haystack, condition);
}
/**
* Standard map function, but returns a new modifiable ArrayList
*
* This returns a new list that contains, for each element of the source collection, its
* image through the passed transform.
* Elements in the source can be null if the transform accepts null inputs.
* Elements in the output can be null if the transform ever returns null.
* This function never returns null. If the source collection is empty, it returns the
* empty list.
* Contract : this method calls the transform function exactly once for each element in the
* list, in iteration order.
*
* @param source the source collection
* @param transform the function to transform the elements
* @param <T> type of source elements
* @param <R> type of destination elements
* @return an unmodifiable list of transformed elements
*/
@NonNull
public static <T, R> ArrayList<R> map(@NonNull final Collection<T> source,
@NonNull final Function<? super T, ? extends R> transform) {
final ArrayList<R> dest = new ArrayList<>(source.size());
for (final T e : source) {
dest.add(transform.apply(e));
}
return dest;
}
/**
* Standard zip function, but returns a new modifiable ArrayList
*
* This returns a list of pairs containing, at each position, a pair of the element from the
* first list at that index and the element from the second list at that index.
* Both lists must be the same size. They may contain null.
*
* The easiest way to visualize what's happening is to think of two lists being laid out next
* to each other and stitched together with a zipper.
*
* Contract : this method will read each element of each list exactly once, in some unspecified
* order. If it throws, it will not read any element.
*
* @param first the first list of elements
* @param second the second list of elements
* @param <T> the type of first elements
* @param <R> the type of second elements
* @return the zipped list
*/
@NonNull
public static <T, R> ArrayList<Pair<T, R>> zip(@NonNull final List<T> first,
@NonNull final List<R> second) {
final int size = first.size();
if (size != second.size()) {
throw new IllegalArgumentException("zip : collections must be the same size");
}
final ArrayList<Pair<T, R>> dest = new ArrayList<>(size);
for (int i = 0; i < size; ++i) {
dest.add(new Pair<>(first.get(i), second.get(i)));
}
return dest;
}
/**
* Returns a new ArrayMap that associates each key with the value at the same index.
*
* Both lists must be the same size.
* Both keys and values may contain null.
* Keys may not contain the same value twice.
*
* Contract : this method will read each element of each list exactly once, but does not
* specify the order, except if it throws in which case the number of reads is undefined.
*
* @param keys The list of keys
* @param values The list of values
* @param <T> The type of keys
* @param <R> The type of values
* @return The associated map
*/
@NonNull
public static <T, R> ArrayMap<T, R> assoc(
@NonNull final List<T> keys, @NonNull final List<R> values) {
final int size = keys.size();
if (size != values.size()) {
throw new IllegalArgumentException("assoc : collections must be the same size");
}
final ArrayMap<T, R> dest = new ArrayMap<>(size);
for (int i = 0; i < size; ++i) {
final T key = keys.get(i);
if (dest.containsKey(key)) {
throw new IllegalArgumentException(
"assoc : keys may not contain the same value twice");
}
dest.put(key, values.get(i));
}
return dest;
}
}

View file

@ -22,6 +22,7 @@ import com.android.testutils.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertSame
@ -133,4 +134,52 @@ class CollectionUtilsTest {
assertSame(CollectionUtils.findLast(listMulti) { it == "E" }, listMulti[7])
assertNull(CollectionUtils.findLast(listMulti) { it == "F" })
}
@Test
fun testMap() {
val listAE = listOf("A", "B", "C", "D", "E", null)
assertEquals(listAE.map { "-$it-" }, CollectionUtils.map(listAE) { "-$it-" })
}
@Test
fun testZip() {
val listAE = listOf("A", "B", "C", "D", "E")
val list15 = listOf(1, 2, 3, 4, 5)
// Normal #zip returns kotlin.Pair, not android.util.Pair
assertEquals(list15.zip(listAE).map { android.util.Pair(it.first, it.second) },
CollectionUtils.zip(list15, listAE))
val listNull = listOf("A", null, "B", "C", "D")
assertEquals(list15.zip(listNull).map { android.util.Pair(it.first, it.second) },
CollectionUtils.zip(list15, listNull))
assertEquals(emptyList<android.util.Pair<Int, Int>>(),
CollectionUtils.zip(emptyList<Int>(), emptyList<Int>()))
assertFailsWith<IllegalArgumentException> {
// Different size
CollectionUtils.zip(listOf(1, 2), list15)
}
}
@Test
fun testAssoc() {
val listADA = listOf("A", "B", "C", "D", "A")
val list15 = listOf(1, 2, 3, 4, 5)
assertEquals(list15.zip(listADA).toMap(), CollectionUtils.assoc(list15, listADA))
// Null key is fine
val assoc = CollectionUtils.assoc(listOf(1, 2, null), listOf("A", "B", "C"))
assertEquals("C", assoc[null])
assertFailsWith<IllegalArgumentException> {
// Same key multiple times
CollectionUtils.assoc(listOf("A", "B", "A"), listOf(1, 2, 3))
}
assertFailsWith<IllegalArgumentException> {
// Same key multiple times, but it's null
CollectionUtils.assoc(listOf(null, "B", null), listOf(1, 2, 3))
}
assertFailsWith<IllegalArgumentException> {
// Different size
CollectionUtils.assoc(listOf(1, 2), list15)
}
}
}