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="http://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 for (int i = list.size(); i > 1; i--) { 102 swap(list, i - 1, rng.nextInt(i)); 103 } 104 } else { 105 // Shuffle as an array 106 final Object[] array = list.toArray(); 107 for (int i = array.length; i > 1; i--) { 108 swap(array, i - 1, rng.nextInt(i)); 109 } 110 111 // Copy back. Use raw types. 112 final ListIterator it = list.listIterator(); 113 for (final Object item : array) { 114 it.next(); 115 it.set(item); 116 } 117 } 118 } 119 120 /** 121 * Shuffles the entries of the given array, using the 122 * <a href="http://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm"> 123 * Fisher-Yates</a> algorithm. 124 * 125 * <p> 126 * The {@code start} and {@code pos} parameters select which part 127 * of the array is randomized and which is left untouched. 128 * </p> 129 * 130 * <p> 131 * Sampling uses {@link UniformRandomProvider#nextInt(int)}. 132 * </p> 133 * 134 * @param <T> Type of the list items. 135 * @param rng Random number generator. 136 * @param list List whose entries will be shuffled (in-place). 137 * @param start Index at which shuffling begins. 138 * @param towardHead Shuffling is performed for index positions between 139 * {@code start} and either the end (if {@code false}) or the beginning 140 * (if {@code true}) of the array. 141 */ 142 public static <T> void shuffle(UniformRandomProvider rng, 143 List<T> list, 144 int start, 145 boolean towardHead) { 146 // Shuffle in-place as a sub-list. 147 if (towardHead) { 148 shuffle(rng, list.subList(0, start + 1)); 149 } else { 150 shuffle(rng, list.subList(start, list.size())); 151 } 152 } 153 154 /** 155 * Swaps the two specified elements in the list. 156 * 157 * @param <T> Type of the list items. 158 * @param list List. 159 * @param i First index. 160 * @param j Second index. 161 */ 162 private static <T> void swap(List<T> list, int i, int j) { 163 final T tmp = list.get(i); 164 list.set(i, list.get(j)); 165 list.set(j, tmp); 166 } 167 168 /** 169 * Swaps the two specified elements in the array. 170 * 171 * @param array Array. 172 * @param i First index. 173 * @param j Second index. 174 */ 175 private static void swap(Object[] array, int i, int j) { 176 final Object tmp = array[i]; 177 array[i] = array[j]; 178 array[j] = tmp; 179 } 180}