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 org.apache.commons.rng.UniformRandomProvider; 021 022/** 023 * Class for representing <a href="https://en.wikipedia.org/wiki/Combination">combinations</a> 024 * of a sequence of integers. 025 * 026 * <p>A combination is a selection of items from a collection, such that (unlike 027 * permutations) the order of selection <strong>does not matter</strong>. This 028 * sampler can be used to generate a combination in an unspecified order and is 029 * faster than the corresponding {@link PermutationSampler}. 030 * 031 * <p>Note that the sample order is unspecified. For example a sample 032 * combination of 2 from 4 may return {@code [0,1]} or {@code [1,0]} as the two are 033 * equivalent, and the order of a given combination may change in subsequent samples. 034 * 035 * <p>The sampler can be used to generate indices to select subsets where the 036 * order of the subset is not important. 037 * 038 * @see PermutationSampler 039 */ 040public class CombinationSampler { 041 /** Domain of the combination. */ 042 private final int[] domain; 043 /** The number of steps of a full shuffle to perform. */ 044 private final int steps; 045 /** 046 * The section to copy the domain from after a partial shuffle. 047 */ 048 private final boolean upper; 049 /** RNG. */ 050 private final UniformRandomProvider rng; 051 052 /** 053 * Creates a generator of combinations. 054 * 055 * <p>The {@link #sample()} method will generate an integer array of 056 * length {@code k} whose entries are selected randomly, without 057 * repetition, from the integers 0, 1, ..., {@code n}-1 (inclusive). 058 * The returned array represents a combination of {@code n} taken 059 * {@code k}. 060 * 061 * <p>In contrast to a permutation, the returned array is <strong>not 062 * guaranteed</strong> to be in a random order. The {@link #sample()} 063 * method returns the array in an unspecified order. 064 * 065 * <p>If {@code n <= 0} or {@code k <= 0} or {@code k > n} then no combination 066 * is required and an exception is raised. 067 * 068 * @param rng Generator of uniformly distributed random numbers. 069 * @param n Domain of the combination. 070 * @param k Size of the combination. 071 * @throws IllegalArgumentException if {@code n <= 0} or {@code k <= 0} or 072 * {@code k > n}. 073 */ 074 public CombinationSampler(UniformRandomProvider rng, 075 int n, 076 int k) { 077 SubsetSamplerUtils.checkSubset(n, k); 078 domain = PermutationSampler.natural(n); 079 // The sample can be optimised by only performing the first k or (n - k) steps 080 // from a full Fisher-Yates shuffle from the end of the domain to the start. 081 // The upper positions will then contain a random sample from the domain. The 082 // lower half is then by definition also a random sample (just not in a random order). 083 // The sample is then picked using the upper or lower half depending which 084 // makes the number of steps smaller. 085 upper = k <= n / 2; 086 steps = upper ? k : n - k; 087 this.rng = rng; 088 } 089 090 /** 091 * Return a combination of {@code k} whose entries are selected randomly, 092 * without repetition, from the integers 0, 1, ..., {@code n}-1 (inclusive). 093 * 094 * <p>The order of the returned array is not guaranteed to be in a random order 095 * as the order of a combination <strong>does not matter</strong>. 096 * 097 * @return a random combination. 098 */ 099 public int[] sample() { 100 return SubsetSamplerUtils.partialSample(domain, steps, rng, upper); 101 } 102}