001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.util;
019
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collections;
023import java.util.Random;
024import org.apache.yetus.audience.InterfaceAudience;
025
026/**
027 * A class that generates random numbers that follow some distribution.
028 * <p>
029 * Copied from <a href="https://issues.apache.org/jira/browse/HADOOP-3315">hadoop-3315 tfile</a>.
030 * Remove after tfile is committed and use the tfile version of this class instead.
031 * </p>
032 */
033@InterfaceAudience.Private
034public class RandomDistribution {
035  /**
036   * Interface for discrete (integer) random distributions.
037   */
038  public interface DiscreteRNG {
039    /**
040     * Get the next random number
041     * @return the next random number.
042     */
043    int nextInt();
044  }
045
046  /**
047   * P(i)=1/(max-min)
048   */
049  public static final class Flat implements DiscreteRNG {
050    private final Random random;
051    private final int min;
052    private final int max;
053
054    /**
055     * Generate random integers from min (inclusive) to max (exclusive) following even distribution.
056     * The basic random number generator. Minimum integer maximum integer (exclusive).
057     */
058    public Flat(Random random, int min, int max) {
059      if (min >= max) {
060        throw new IllegalArgumentException("Invalid range");
061      }
062      this.random = random;
063      this.min = min;
064      this.max = max;
065    }
066
067    /**
068     * @see DiscreteRNG#nextInt()
069     */
070    @Override
071    public int nextInt() {
072      return random.nextInt(max - min) + min;
073    }
074  }
075
076  /**
077   * Zipf distribution. The ratio of the probabilities of integer i and j is defined as follows:
078   * P(i)/P(j)=((j-min+1)/(i-min+1))^sigma.
079   */
080  public static final class Zipf implements DiscreteRNG {
081    private static final double DEFAULT_EPSILON = 0.001;
082    private final Random random;
083    private final ArrayList<Integer> k;
084    private final ArrayList<Double> v;
085
086    /**
087     * Constructor The random number generator. minimum integer (inclusvie) maximum integer
088     * (exclusive) parameter sigma. (sigma > 1.0)
089     */
090    public Zipf(Random r, int min, int max, double sigma) {
091      this(r, min, max, sigma, DEFAULT_EPSILON);
092    }
093
094    /**
095     * Constructor. The random number generator. minimum integer (inclusvie) maximum integer
096     * (exclusive) parameter sigma. (sigma > 1.0) Allowable error percentage (0 < epsilon < 1.0).
097     */
098    public Zipf(Random r, int min, int max, double sigma, double epsilon) {
099      if ((max <= min) || (sigma <= 1) || (epsilon <= 0) || (epsilon >= 0.5)) {
100        throw new IllegalArgumentException("Invalid arguments");
101      }
102      random = r;
103      k = new ArrayList<>();
104      v = new ArrayList<>();
105
106      double sum = 0;
107      int last = -1;
108      for (int i = min; i < max; ++i) {
109        sum += Math.exp(-sigma * Math.log(i - min + 1));
110        if ((last == -1) || i * (1 - epsilon) > last) {
111          k.add(i);
112          v.add(sum);
113          last = i;
114        }
115      }
116
117      if (last != max - 1) {
118        k.add(max - 1);
119        v.add(sum);
120      }
121
122      v.set(v.size() - 1, 1.0);
123
124      for (int i = v.size() - 2; i >= 0; --i) {
125        v.set(i, v.get(i) / sum);
126      }
127    }
128
129    /**
130     * @see DiscreteRNG#nextInt()
131     */
132    @Override
133    public int nextInt() {
134      double d = random.nextDouble();
135      int idx = Collections.binarySearch(v, d);
136
137      if (idx > 0) {
138        ++idx;
139      } else {
140        idx = -(idx + 1);
141      }
142
143      if (idx >= v.size()) {
144        idx = v.size() - 1;
145      }
146
147      if (idx == 0) {
148        return k.get(0);
149      }
150
151      int ceiling = k.get(idx);
152      int lower = k.get(idx - 1);
153
154      return ceiling - random.nextInt(ceiling - lower);
155    }
156  }
157
158  /**
159   * Binomial distribution. P(k)=select(n, k)*p^k*(1-p)^(n-k) (k = 0, 1, ..., n)
160   * P(k)=select(max-min-1, k-min)*p^(k-min)*(1-p)^(k-min)*(1-p)^(max-k-1)
161   */
162  public static final class Binomial implements DiscreteRNG {
163    private final Random random;
164    private final int min;
165    private final int n;
166    private final double[] v;
167
168    private static double select(int n, int k) {
169      double ret = 1.0;
170      for (int i = k + 1; i <= n; ++i) {
171        ret *= (double) i / (i - k);
172      }
173      return ret;
174    }
175
176    private static double power(double p, int k) {
177      return Math.exp(k * Math.log(p));
178    }
179
180    /**
181     * Generate random integers from min (inclusive) to max (exclusive) following Binomial
182     * distribution. The basic random number generator. Minimum integer maximum integer (exclusive).
183     * parameter.
184     */
185    public Binomial(Random random, int min, int max, double p) {
186      if (min >= max) {
187        throw new IllegalArgumentException("Invalid range");
188      }
189      this.random = random;
190      this.min = min;
191      this.n = max - min - 1;
192      if (n > 0) {
193        v = new double[n + 1];
194        double sum = 0.0;
195        for (int i = 0; i <= n; ++i) {
196          sum += select(n, i) * power(p, i) * power(1 - p, n - i);
197          v[i] = sum;
198        }
199        for (int i = 0; i <= n; ++i) {
200          v[i] /= sum;
201        }
202      } else {
203        v = null;
204      }
205    }
206
207    /**
208     * @see DiscreteRNG#nextInt()
209     */
210    @Override
211    public int nextInt() {
212      if (v == null) {
213        return min;
214      }
215      double d = random.nextDouble();
216      int idx = Arrays.binarySearch(v, d);
217      if (idx > 0) {
218        ++idx;
219      } else {
220        idx = -(idx + 1);
221      }
222
223      if (idx >= v.length) {
224        idx = v.length - 1;
225      }
226      return idx + min;
227    }
228  }
229}