ListSampler.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.rng.sampling;
import java.util.List;
import java.util.ListIterator;
import java.util.RandomAccess;
import java.util.ArrayList;
import org.apache.commons.rng.UniformRandomProvider;
/**
* Sampling from a {@link List}.
*
* <p>This class also contains utilities for shuffling a {@link List} in-place.</p>
*
* @since 1.0
*/
public final class ListSampler {
/**
* The size threshold for using the random access algorithm
* when the list does not implement java.util.RandomAccess.
*/
private static final int RANDOM_ACCESS_SIZE_THRESHOLD = 4;
/**
* Class contains only static methods.
*/
private ListSampler() {}
/**
* Generates a list of size {@code k} whose entries are selected
* randomly, without repetition, from the items in the given
* {@code collection}.
*
* <p>
* Sampling is without replacement; but if the source collection
* contains identical objects, the sample may include repeats.
* </p>
*
* <p>
* Sampling uses {@link UniformRandomProvider#nextInt(int)}.
* </p>
*
* @param <T> Type of the list items.
* @param rng Generator of uniformly distributed random numbers.
* @param collection List to be sampled from.
* @param k Size of the returned sample.
* @throws IllegalArgumentException if {@code k <= 0} or
* {@code k > collection.size()}.
* @return a shuffled sample from the source collection.
*/
public static <T> List<T> sample(UniformRandomProvider rng,
List<T> collection,
int k) {
final int n = collection.size();
final PermutationSampler p = new PermutationSampler(rng, n, k);
final List<T> result = new ArrayList<>(k);
final int[] index = p.sample();
for (int i = 0; i < k; i++) {
result.add(collection.get(index[i]));
}
return result;
}
/**
* Shuffles the entries of the given array, using the
* <a href="http://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm">
* Fisher-Yates</a> algorithm.
*
* <p>
* Sampling uses {@link UniformRandomProvider#nextInt(int)}.
* </p>
*
* @param <T> Type of the list items.
* @param rng Random number generator.
* @param list List whose entries will be shuffled (in-place).
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static <T> void shuffle(UniformRandomProvider rng,
List<T> list) {
if (list instanceof RandomAccess || list.size() < RANDOM_ACCESS_SIZE_THRESHOLD) {
// Shuffle list in-place
for (int i = list.size(); i > 1; i--) {
swap(list, i - 1, rng.nextInt(i));
}
} else {
// Shuffle as an array
final Object[] array = list.toArray();
for (int i = array.length; i > 1; i--) {
swap(array, i - 1, rng.nextInt(i));
}
// Copy back. Use raw types.
final ListIterator it = list.listIterator();
for (final Object item : array) {
it.next();
it.set(item);
}
}
}
/**
* Shuffles the entries of the given array, using the
* <a href="http://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm">
* Fisher-Yates</a> algorithm.
*
* <p>
* The {@code start} and {@code pos} parameters select which part
* of the array is randomized and which is left untouched.
* </p>
*
* <p>
* Sampling uses {@link UniformRandomProvider#nextInt(int)}.
* </p>
*
* @param <T> Type of the list items.
* @param rng Random number generator.
* @param list List whose entries will be shuffled (in-place).
* @param start Index at which shuffling begins.
* @param towardHead Shuffling is performed for index positions between
* {@code start} and either the end (if {@code false}) or the beginning
* (if {@code true}) of the array.
*/
public static <T> void shuffle(UniformRandomProvider rng,
List<T> list,
int start,
boolean towardHead) {
// Shuffle in-place as a sub-list.
if (towardHead) {
shuffle(rng, list.subList(0, start + 1));
} else {
shuffle(rng, list.subList(start, list.size()));
}
}
/**
* Swaps the two specified elements in the list.
*
* @param <T> Type of the list items.
* @param list List.
* @param i First index.
* @param j Second index.
*/
private static <T> void swap(List<T> list, int i, int j) {
final T tmp = list.get(i);
list.set(i, list.get(j));
list.set(j, tmp);
}
/**
* Swaps the two specified elements in the array.
*
* @param array Array.
* @param i First index.
* @param j Second index.
*/
private static void swap(Object[] array, int i, int j) {
final Object tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}