001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.rng.sampling; 019 020import java.util.List; 021import java.util.ListIterator; 022import java.util.RandomAccess; 023import java.util.ArrayList; 024 025import org.apache.commons.rng.UniformRandomProvider; 026 027/** 028 * Sampling from a {@link List}. 029 * 030 * <p>This class also contains utilities for shuffling a {@link List} in-place.</p> 031 * 032 * @since 1.0 033 */ 034public final class ListSampler { 035 /** 036 * The size threshold for using the random access algorithm 037 * when the list does not implement java.util.RandomAccess. 038 */ 039 private static final int RANDOM_ACCESS_SIZE_THRESHOLD = 4; 040 041 /** 042 * Class contains only static methods. 043 */ 044 private ListSampler() {} 045 046 /** 047 * Generates a list of size {@code k} whose entries are selected 048 * randomly, without repetition, from the items in the given 049 * {@code collection}. 050 * 051 * <p> 052 * Sampling is without replacement; but if the source collection 053 * contains identical objects, the sample may include repeats. 054 * </p> 055 * 056 * <p> 057 * Sampling uses {@link UniformRandomProvider#nextInt(int)}. 058 * </p> 059 * 060 * @param <T> Type of the list items. 061 * @param rng Generator of uniformly distributed random numbers. 062 * @param collection List to be sampled from. 063 * @param k Size of the returned sample. 064 * @throws IllegalArgumentException if {@code k <= 0} or 065 * {@code k > collection.size()}. 066 * @return a shuffled sample from the source collection. 067 */ 068 public static <T> List<T> sample(UniformRandomProvider rng, 069 List<T> collection, 070 int k) { 071 final int n = collection.size(); 072 final PermutationSampler p = new PermutationSampler(rng, n, k); 073 final List<T> result = new ArrayList<>(k); 074 final int[] index = p.sample(); 075 076 for (int i = 0; i < k; i++) { 077 result.add(collection.get(index[i])); 078 } 079 080 return result; 081 } 082 083 /** 084 * Shuffles the entries of the given array, using the 085 * <a href="https://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm"> 086 * Fisher-Yates</a> algorithm. 087 * 088 * <p> 089 * Sampling uses {@link UniformRandomProvider#nextInt(int)}. 090 * </p> 091 * 092 * @param <T> Type of the list items. 093 * @param rng Random number generator. 094 * @param list List whose entries will be shuffled (in-place). 095 */ 096 @SuppressWarnings({"rawtypes", "unchecked"}) 097 public static <T> void shuffle(UniformRandomProvider rng, 098 List<T> list) { 099 if (list instanceof RandomAccess || list.size() < RANDOM_ACCESS_SIZE_THRESHOLD) { 100 // Shuffle list in-place 101 ArraySampler.shuffle(rng, list); 102 } else { 103 // Shuffle as an array 104 final Object[] array = list.toArray(); 105 ArraySampler.shuffle(rng, array); 106 107 // Copy back. Use raw types. 108 final ListIterator it = list.listIterator(); 109 for (final Object item : array) { 110 it.next(); 111 it.set(item); 112 } 113 } 114 } 115 116 /** 117 * Shuffles the entries of the given array, using the 118 * <a href="https://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm"> 119 * Fisher-Yates</a> algorithm. 120 * 121 * <p> 122 * The {@code start} and {@code pos} parameters select which part 123 * of the array is randomized and which is left untouched. 124 * </p> 125 * 126 * <p> 127 * Sampling uses {@link UniformRandomProvider#nextInt(int)}. 128 * </p> 129 * 130 * @param <T> Type of the list items. 131 * @param rng Random number generator. 132 * @param list List whose entries will be shuffled (in-place). 133 * @param start Index at which shuffling begins. 134 * @param towardHead Shuffling is performed for index positions between 135 * {@code start} and either the end (if {@code false}) or the beginning 136 * (if {@code true}) of the array. 137 */ 138 public static <T> void shuffle(UniformRandomProvider rng, 139 List<T> list, 140 int start, 141 boolean towardHead) { 142 // Shuffle in-place as a sub-list. 143 if (towardHead) { 144 shuffle(rng, list.subList(0, start + 1)); 145 } else { 146 shuffle(rng, list.subList(start, list.size())); 147 } 148 } 149}