//Prize and Probablity (真黑心啊这几率。。。)var prizeMap = map[string]float64{ "gold": 0.02, "silver": 0.03, "bronze": 0.05, "nothing" : 0.9,}func luckyDraw(prizeMap map[string]float64) string{ rand.Seed(time.Now().UnixNano()) random := rand.Float64() for k, v := range prizeMap { if random < v { return k } random -= v } return "nothing"}
之所以Binary Search不算最优秀的一种接法是因为在二分查找,移动指针的时候很容易出现+1,-1等指针错误或者index out of range error。所以我思考了很久更优秀的解法,也问了朋友能不能提供一些想法,可是我们都没有更好的灵感。最后,朋友发给的我一篇文章令我非常动容,令我感触最深。更优秀的算法出现了:这篇文章介绍了两种极致的加权随机采样算法,它们太优美了;让人不得不惊叹创造者的思想。所以下面的部分我主要用通俗的方法来介绍这两种算法的魅力。
//interface{} has the format of prize name and weighttype PrizeCollection [][]interface{}var a = PrizeCollection{ {"Dyson",1}, {"iPhone",1}, {"XBox", 2},}func getHopScotchFunc(collection PrizeCollection) func() string { //Reverse Sort the Weights sort.Slice(collection, func(i, j int) bool { return collection[j][1].(int) < collection[i][1].(int) }) sum := 0 collectionWeightSum := make([]int, len(collection)) for i := 0; i < len(collection); i++ { sum += collection[i][1].(int) collectionWeightSum[i] = sum } rand.Seed(time.Now().UnixNano()) //return the func that used to do lucky draw return func() string { target := rand.Intn(collectionWeightSum[len(collectionWeightSum)-1]) guessIndex := 0 for true { if collectionWeightSum[guessIndex] > target { break } //Retrieve the actual weight of that index currentWeight := collection[guessIndex][1].(int) //Calculate the difference between the current sum and the target hopDistance := target - collectionWeightSum[guessIndex] //Calculate the safe distance (indice) to jump hopIndex := 1 + hopDistance/currentWeight //Jump guessIndex += hopIndex } return collection[guessIndex][0].(string) }}
func getAliasFunc(collection PrizeCollection) func()string { totalPrizeNum := len(collection) sum := 0 for i := 0; i < len(collection); i++ { sum += collection[i][1].(int) } //Get Average average := float64(sum) / float64(totalPrizeNum) //Aliases is a array of [float, int] so we use interface{} aliases := make([][]interface{}, totalPrizeNum) //Initialisaition for i := 0; i < totalPrizeNum; i++ { aliases[i] = []interface{}{1.0, 0.0} } //U can check the explanation first before coming back~ bigWeights := make([][]interface{},0) smallWeights := make([][]interface{},0) for index, prizeItem := range collection { if float64(prizeItem[1].(int)) < average { smallWeights = append(smallWeights, []interface{}{index, float64(prizeItem[1].(int)) / average }) } else { bigWeights = append(bigWeights, []interface{}{index, float64(prizeItem[1].(int)) / average }) } } bigWeightsPosition := 0 for i := 0; i < len(smallWeights); i++ { aliases[smallWeights[i][0].(int)] = []interface{}{smallWeights[i][1].(float64), bigWeights[bigWeightsPosition][0].(int)} bigWeights[bigWeightsPosition][1] = bigWeights[bigWeightsPosition][1].(float64) - (1 - smallWeights[i][1].(float64)) if bigWeights[bigWeightsPosition][1].(float64) <= 1 { smallWeights = append(smallWeights, bigWeights[bigWeightsPosition]) bigWeightsPosition++ if bigWeightsPosition >= len(bigWeights) { break } } } //---------------------------return the func that used to do lucky draw rand.Seed(time.Now().UnixNano()) return func() string { target := rand.Float64()*float64(len(collection)) targetAlias := int(target) targetWeight := target - float64(targetAlias) if targetWeight < aliases[targetAlias][0].(float64) { return collection[targetAlias][0].(string) } else { return collection[aliases[targetAlias][1].(int)][0].(string) } }}
由于golang缺乏复杂的数据结构和好用的mothod,代码比较不可读,奉上python版本:
CODE
def prepare_aliased_randomizer(weights): N = len(weights) avg = sum(weights)/N aliases = [(1, None)]*N smalls = ((i, w/avg) for i,w in enumerate(weights) if w < avg) bigs = ((i, w/avg) for i,w in enumerate(weights) if w >= avg) small, big = next(smalls, None), next(bigs, None) while big and small: aliases[small[0]] = (small[1], big[0]) big = (big[0], big[1] - (1-small[1])) if big[1] < 1: small = big big = next(bigs, None) else: small = next(smalls, None) def weighted_random(): r = random()*N i = int(r) odds, alias = aliases[i] return alias if (r-i) > odds else i return weighted_random