SubsetSamplerUtils.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 org.apache.commons.rng.UniformRandomProvider;

/**
 * Utility class for selecting a subset of a sequence of integers.
 */
final class SubsetSamplerUtils {

    /** No public construction. */
    private SubsetSamplerUtils() {}

    /**
     * Checks the subset of length {@code k} from {@code n} is valid.
     *
     * <p>If {@code n <= 0} or {@code k <= 0} or {@code k > n} then no subset
     * is required and an exception is raised.</p>
     *
     * @param n   Size of the set.
     * @param k   Size of the subset.
     * @throws IllegalArgumentException if {@code n <= 0} or {@code k <= 0} or
     *                                  {@code k > n}.
     */
    static void checkSubset(int n,
                            int k) {
        if (n <= 0) {
            throw new IllegalArgumentException("n <= 0 : n=" + n);
        }
        if (k <= 0) {
            throw new IllegalArgumentException("k <= 0 : k=" + k);
        }
        if (k > n) {
            throw new IllegalArgumentException("k > n : k=" + k + ", n=" + n);
        }
    }

    /**
     * Perform a partial Fisher-Yates shuffle of the domain in-place and return
     * either the upper fully shuffled section or the remaining lower partially
     * shuffled section.
     *
     * <p>The returned combination will have a length of {@code steps} for
     * {@code upper=true}, or {@code domain.length - steps} otherwise.</p>
     *
     * <p>Sampling uses {@link UniformRandomProvider#nextInt(int)}.</p>
     *
     * @param domain The domain.
     * @param steps  The number of shuffle steps.
     * @param rng    Generator of uniformly distributed random numbers.
     * @param upper  Set to true to return the upper fully shuffled section.
     * @return a random combination.
     */
    static int[] partialSample(int[] domain,
                               int steps,
                               UniformRandomProvider rng,
                               boolean upper) {
        // Shuffle from the end but limit to the number of steps.
        // Note: If 'steps' is the full length of the array then the final
        // swap is redundant so can be skipped.
        int swapCount = Math.min(steps, domain.length - 1);
        for (int i = domain.length - 1; swapCount > 0; i--, swapCount--) {
            // Swap index i with any position down to 0 (including itself)
            swap(domain, i, rng.nextInt(i + 1));
        }
        final int size = upper ? steps : domain.length - steps;
        final int from = upper ? domain.length - steps : 0;
        final int[] result = new int[size];
        System.arraycopy(domain, from, result, 0, size);
        return result;
    }

    /**
     * Swaps the two specified elements in the specified array.
     *
     * @param array the array
     * @param i     the first index
     * @param j     the second index
     */
    static void swap(int[] array, int i, int j) {
        final int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
}