While the selected answer works, it is unfortunately asymptotically slow for your use case. Instead, you can use something called Alias Sampling . A sampling algorithm (or alias) is a method used to select items with a weighted distribution. If the selection weights of these elements do not change, you can make a selection within O (1) !. If this is not the case, you can get the amortized time O (1) if the ratio between the number of options you choose and the changes you make to the alias table (change weights) are high. The currently selected answer is offered by the O (N) algorithm, the next best thing is O (log (N)) taking into account the sorted probabilities and a binary search , but nothing will beat the O (1) time that I suggested.
This site provides a good overview of the alias method, which is mainly a language agnostic. Essentially, you create a table in which each record represents the result of two probabilities. There is one threshold for each record in the table, below the threshold you get one value, above you get a different value. You distribute high probabilities into several table values to create a probability graph with an area of one for all probabilities.
Say you have probabilities A, B, C, and D, which have values of 0.1, 0.1, 0.1, and 0.7, respectively. The alias method would spread the probability of 0.7 for everyone else. One index will correspond to each probability, where you will have 0.1 and 0.15 for ABC and 0.25 for index D. At the same time, you normalize each probability so that you get 0.4 probability to get probability A and 0.6, so that get D in index A (0,1 / (0,1 + 0,15) and 0,15 / (0,1 + 0,15) respectively), as well as B and C index and 100% probability to get index D in D (0.25 / 0.25 is 1).
Given the unbiased unified PRNG (Math.Random ()) for indexing, you get an equal probability of choosing each index, but you also flip coins for each index, which provides a weighted probability. You have a 25% chance of landing on slot A or D, but in this case you have only a 40% chance of dialing A and 60% D..40 * .25 * .25 = 0.1, our initial probability, and if you add all the probabilities D scattered by other indices, you will get .70 again.
To make a random selection, you only need to create a random index from 0 to N, and then make a coin flip, no matter how many items you add, this is a very fast and constant cost. Creating an alias table doesn’t take too many lines of code, my python version takes 80 lines, including import statements and line breaks, and the version presented in the Pandas article is the same size (and this is C ++)
For your java implementation, you can correlate the probabilities and indices of the list of arrays with your functions that you must perform by creating an array of functions that are executed when each of them is indexed, you can also use function objects ( functors ), which have a method that you use to passing parameters to execute.
ArrayList<(YourFunctionObject)> function_list; // add functions AliasSampler aliassampler = new AliasSampler(listOfProbabilities); // somewhere later with some type T and some parameter values. int index = aliassampler.sampleIndex(); T result = function_list[index].apply(parameters);
EDIT:
I created a version in java of the AliasSampler method using classes, this uses an example index method and should be able to be used as described above.
import java.util.ArrayList; import java.util.Collections; import java.util.Random; public class AliasSampler { private ArrayList<Double> binaryProbabilityArray; private ArrayList<Integer> aliasIndexList; AliasSampler(ArrayList<Double> probabilities){ // java 8 needed here assert(DoubleStream.of(probabilities).sum() == 1.0); int n = probabilities.size(); // probabilityArray is the list of probabilities, this is the incoming probabilities scaled // by the number of probabilities. This allows us to figure out which probabilities need to be spread // to others since they are too large, ie [0.1 0.1 0.1 0.7] = [0.4 0.4 0.4 2.80] ArrayList<Double> probabilityArray; for(Double probability : probabilities){ probabilityArray.add(probability); } binaryProbabilityArray = new ArrayList<Double>(Collections.nCopies(n, 0.0)); aliasIndexList = new ArrayList<Integer>(Collections.nCopies(n, 0)); ArrayList<Integer> lessThanOneIndexList = new ArrayList<Integer>(); ArrayList<Integer> greaterThanOneIndexList = new ArrayList<Integer>(); for(int index = 0; index < probabilityArray.size(); index++){ double probability = probabilityArray.get(index); if(probability < 1.0){ lessThanOneIndexList.add(index); } else{ greaterThanOneIndexList.add(index); } } // while we still have indices to check for in each list, we attempt to spread the probability of those larger // what this ends up doing in our first example is taking greater than one elements (2.80) and removing 0.6, // and spreading it to different indices, so (((2.80 - 0.6) - 0.6) - 0.6) will equal 1.0, and the rest will // be 0.4 + 0.6 = 1.0 as well. while(lessThanOneIndexList.size() != 0 && greaterThanOneIndexList.size() != 0){ //https://stackoverflow.com/questions/16987727/removing-last-object-of-arraylist-in-java // last element removal is equivalent to pop, java does this in constant time int lessThanOneIndex = lessThanOneIndexList.remove(lessThanOneIndexList.size() - 1); int greaterThanOneIndex = greaterThanOneIndexList.remove(greaterThanOneIndexList.size() - 1); double probabilityLessThanOne = probabilityArray.get(lessThanOneIndex); binaryProbabilityArray.set(lessThanOneIndex, probabilityLessThanOne); aliasIndexList.set(lessThanOneIndex, greaterThanOneIndex); probabilityArray.set(greaterThanOneIndex, probabilityArray.get(greaterThanOneIndex) + probabilityLessThanOne - 1); if(probabilityArray.get(greaterThanOneIndex) < 1){ lessThanOneIndexList.add(greaterThanOneIndex); } else{ greaterThanOneIndexList.add(greaterThanOneIndex); } } //if there are any probabilities left in either index list, they can't be spread across the other //indicies, so they are set with probability 1.0. They still have the probabilities they should at this step, it works out mathematically. while(greaterThanOneIndexList.size() != 0){ int greaterThanOneIndex = greaterThanOneIndexList.remove(greaterThanOneIndexList.size() - 1); binaryProbabilityArray.set(greaterThanOneIndex, 1.0); } while(lessThanOneIndexList.size() != 0){ int lessThanOneIndex = lessThanOneIndexList.remove(lessThanOneIndexList.size() - 1); binaryProbabilityArray.set(lessThanOneIndex, 1.0); } } public int sampleIndex(){ int index = new Random().nextInt(binaryProbabilityArray.size()); double r = Math.random(); if( r < binaryProbabilityArray.get(index)){ return index; } else{ return aliasIndexList.get(index); } } }