Get pseudo-random item with given probability

后端 未结 3 1074
终归单人心
终归单人心 2021-01-23 12:44

I want to give the user a prize when he signs in; but it needs to be there some rare prizes so I want to appear prizes with different chances to appear using percents

i w

相关标签:
3条回答
  • 2021-01-23 12:58

    You can create a function to get weighted random results, something like this:

    const prizes = [[50, 'flower'], [30, 'book'], [20, 'mobile']]
    
    const total = prizes.reduce((sum, [weight]) => sum + weight, 0)
    
    const getPrize = () => {
        const rnd = Math.random() * total
        let accumulator = 0
    
        for (const [weight, item] of prizes) {
            accumulator += weight
    
            if (rnd < accumulator) {
                return item
            }
        }
    }
    
    // check frequencies of each result
    
    const results = {}
    
    for (let i = 0; i < 100000; ++i) {
        const prize = getPrize()
        
        results[prize] = (results[prize] || 0) + 1
    }
    
    console.log(results)

    This will work regardless of whether the weights add up to 100, whether they're integers, and so on.

    0 讨论(0)
  • 2021-01-23 13:17

    'Right off the top of my head'-approach would be to prepare an array where each source item occurs the number of times that corresponds to respective probability and pick random item out of that array (assuming probability value has no more than 2 decimal places):

    // main function
    const getPseudoRandom = items => {
      const {min, random} = Math,
            commonMultiplier = 100,
            itemBox = []
      for(item in items){
        for(let i = 0; i < items[item]*commonMultiplier; i++){
          const randomPosition = 0|random()*itemBox.length  
          itemBox.splice(randomPosition, 0, item)
        }
      }        
      return itemBox[0|random()*itemBox.length]
    }
    
    // test of random outcomes distribution
    const outcomes = Array(1000)
            .fill()
            .map(_ => getPseudoRandom({'flower': 0.5, 'book': 0.3, 'mobile': 0.2})),
          
          distribution = outcomes.reduce((acc, item, _, s) => 
            (acc[item] = (acc[item]||0)+100/s.length, acc), {})
    
    console.log(distribution)
    .as-console-wrapper{min-height:100%;}

    While above approach may seem easy to comprehend and deploy, you may consider another one - build up the sort of probability ranges of respective width and have your random value falling into one of those - the wider the range, the greater probability:

    const items = {'flower': 0.5, 'book': 0.2, 'mobile': 0.2, '1mUSD': 0.1},
    
          // main function
          getPseudoRandom = items => {
            let totalWeight = 0,
                ranges = [],
                rnd = Math.random()
            for(const itemName in items){      
              ranges.push({
                itemName,
                max: totalWeight += items[itemName]
              })
            }           
            return ranges
                    .find(({max}) => max > rnd*totalWeight)
                    .itemName
          },
          
          // test of random outcomes distribution
          outcomes = Array(1000)
            .fill()
            .map(_ => getPseudoRandom(items)),
    
          distribution = outcomes.reduce((acc, item, _, s) => 
            (acc[item] = (acc[item]||0)+100/s.length, acc), {})
    
    console.log(distribution)

    0 讨论(0)
  • 2021-01-23 13:20

    "Certain probability" and "random" could lead to different approaches!

    If you want random each time, something like:

    let chances = [[0.2,'mobile'],[0.5,'book'],[1.0,'flower]]
    let val = Math.random() // floating number from 0 to 1.0
    let result = chances.find( c => c[0] <= val )[1] 
    

    This will give a random result each time. It could be possible to get 'mobile' 100 times in a row! Rare, of course, but a good random number generate will let that happen.

    But perhaps you want to ensure that, in 100 results, you only hand out 20 mobiles, 30 books, and 50 flowers. Then you might want a "random array" for each user. Pre-fill the all the slots and remove them as they are used. Something like:

    // when setting up a new user
    let userArray = []
    let chances = [[20,'mobile'],[30,'book'],[50,'flower]]
    changes.forEach( c => {
      for(let i = 0; i < c[0]; i++) userArray.push(c[1])
    })
    // save userArray, which has exactly 100 values
    // then, when picking a random value for a user, find an index in the current length
    let index = Math.floor(Math.random() * userArray.length)
    let result = userArray[index]
    userArray.splice(index,1) // modify and save userArray for next login
    if(userArray.length === 0) reinitializeUserArray()
    

    There are different approaches to this, but just some ideas to get you started.

    0 讨论(0)
提交回复
热议问题