View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.rng.sampling;
19  
20  import java.util.List;
21  import java.util.ListIterator;
22  import java.util.RandomAccess;
23  import java.util.ArrayList;
24  
25  import org.apache.commons.rng.UniformRandomProvider;
26  
27  /**
28   * Sampling from a {@link List}.
29   *
30   * <p>This class also contains utilities for shuffling a {@link List} in-place.</p>
31   *
32   * @since 1.0
33   */
34  public final class ListSampler {
35      /**
36       * The size threshold for using the random access algorithm
37       * when the list does not implement java.util.RandomAccess.
38       */
39      private static final int RANDOM_ACCESS_SIZE_THRESHOLD = 4;
40  
41      /**
42       * Class contains only static methods.
43       */
44      private ListSampler() {}
45  
46      /**
47       * Generates a list of size {@code k} whose entries are selected
48       * randomly, without repetition, from the items in the given
49       * {@code collection}.
50       *
51       * <p>
52       * Sampling is without replacement; but if the source collection
53       * contains identical objects, the sample may include repeats.
54       * </p>
55       *
56       * <p>
57       * Sampling uses {@link UniformRandomProvider#nextInt(int)}.
58       * </p>
59       *
60       * @param <T> Type of the list items.
61       * @param rng Generator of uniformly distributed random numbers.
62       * @param collection List to be sampled from.
63       * @param k Size of the returned sample.
64       * @throws IllegalArgumentException if {@code k <= 0} or
65       * {@code k > collection.size()}.
66       * @return a shuffled sample from the source collection.
67       */
68      public static <T> List<T> sample(UniformRandomProvider rng,
69                                       List<T> collection,
70                                       int k) {
71          final int n = collection.size();
72          final PermutationSampler p = new PermutationSampler(rng, n, k);
73          final List<T> result = new ArrayList<>(k);
74          final int[] index = p.sample();
75  
76          for (int i = 0; i < k; i++) {
77              result.add(collection.get(index[i]));
78          }
79  
80          return result;
81      }
82  
83      /**
84       * Shuffles the entries of the given array, using the
85       * <a href="http://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm">
86       * Fisher-Yates</a> algorithm.
87       *
88       * <p>
89       * Sampling uses {@link UniformRandomProvider#nextInt(int)}.
90       * </p>
91       *
92       * @param <T> Type of the list items.
93       * @param rng Random number generator.
94       * @param list List whose entries will be shuffled (in-place).
95       */
96      @SuppressWarnings({"rawtypes", "unchecked"})
97      public static <T> void shuffle(UniformRandomProvider rng,
98                                     List<T> list) {
99          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 }