Here's an algorithm (in C#) that can select random weighted element from any sequence, only iterating through it once:
public static T Random<T>(this IEnumerable<T> enumerable, Func<T, int> weightFunc)
{
int totalWeight = 0; // this stores sum of weights of all elements before current
T selected = default(T); // currently selected element
foreach (var data in enumerable)
{
int weight = weightFunc(data); // weight of current element
int r = Random.Next(totalWeight + weight); // random value
if (r >= totalWeight) // probability of this is weight/(totalWeight+weight)
selected = data; // it is the probability of discarding last selected element and selecting current one instead
totalWeight += weight; // increase weight sum
}
return selected; // when iterations end, selected is some element of sequence.
}
This is based on the following reasoning: let's select first element of our sequence as "current result"; then, on each iteration, either keep it or discard and choose new element as current. We can calculate the probability of any given element to be selected in the end as a product of all probabilities that it wouldn't be discarded in subsequent steps, times probability that it would be selected in the first place. If you do the math, you'd see that this product simplifies to (weight of element)/(sum of all weights), which is exactly what we need!
Since this method only iterates over the input sequence once, it works even with obscenely large sequences, provided that sum of weights fits into an int (or you can choose some bigger type for this counter)