【问题标题】:Weighted random number generation in JavascriptJavascript中的加权随机数生成
【发布时间】:2012-09-13 00:26:17
【问题描述】:

我有一个看起来像这样的数组:

[
    {
        plays: 0,
        otherData: someValues
    }, {
        plays: 4,
        otherData: someValues
    }, {
        plays: 1,
        otherData: someValues
    }, {
        plays: 2,
        otherData: someValues
    } {
        plays: 9,
        otherData: someValues
    }, {
        plays: 7,
        otherData: someValues
    }, {
        plays: 5,
        otherData: someValues
    }, {
        plays: 0,
        otherData: someValues
    }, {
        plays: 8,
        otherData: someValues
    }
]

这是有关播放列表中歌曲的一系列信息,其中plays 是歌曲的播放次数。我试图提出一个加权随机数生成器,它将选择一个元素的索引,加权使得播放较少的歌曲更有可能被选中。这是我现在拥有的代码:

function pickRandom(){
    var oldIndex = index;
    if(songs.length <= 1)
        return index = 0;
    var unheard = [];
    for(i in songs){
        if(!songs[i].plays)
            unheard.push(i);
    }if(unheard.length > 0)
        return index = unheard[Math.round(Math.random() * (unheard.length - 1))];
    var tries = 0;
    while(index == oldIndex && tries < 100){
        index = Math.round(Math.random() * (songs.length - 1));
        tries++;
    }return index;
}

这个解决方案有很多我不满意的地方。首先,它的权重并没有太大,因为它实际上只是选择一首未播放的歌曲,或者如果数组中的所有内容都至少播放过一次,则可以选择任何旧的随机歌曲。其次,它创建了一个新数组,而且由于播放列表有时会包含数百首歌曲,所以如果可能的话,我想避免这样做。

我能想出的最接近的解决方案是根据每个元素的 plays 值将每个元素多次复制到一个新数组中,然后从中挑选一个元素,但这会加剧创建新数组的问题数组,因为第二个数组很容易达到数千个元素。我会非常感谢任何帮助或建议;即使是伪代码也可以。

【问题讨论】:

  • 假设随机函数是正态分布的,我认为这个算法什么都不做,完全相当于只做:selectedIndex = Math.floor(Math.random() * numberOfElement);。就像反复掷骰子一样,如果 one 尚未被看到,那么它在下一次掷骰中被看到的机会将不断增加。如果随机函数不是正态分布的,这个算法会有所帮助,但也不会是正态分布的。
  • 在上面编辑:我的坏它仍然有帮助,因为用户仍然可以播放他想要的歌曲并因此改变播放次数。我只是想强调一个事实,即随机确实希望所有数字平均存在相同的时间。

标签: javascript random


【解决方案1】:

我会用循环做你想做的事情。合计列表中任何歌曲的最大播放次数,然后通过计算反向加权的数字并从反向总数中选择来反转概率。像这样的:

function pickRandom(myArray) {
    var maxPlays = 0, reverseTotPlays = 0, ipl, picked, revAcc = 0;

    // Empty array or bad input param
    if (!myArray || !myArray.length) {
        return -1;
    }

    // Calculate the max plays for any song in the list
    for (ipl = 0;  ipl < myArray.length;  ++ipl) {
        if (myArray[ipl].plays > maxPlays) {
            maxPlays = myArray[ipl].plays;
        }
    }
    maxPlays += 1;   // Avoid excluding max songs

    // Calculate the reverse weighted total plays
    for (ipl = 0;  ipl < myArray.length;  ++ipl) {
        reverseTotPlays += maxPlays - myArray[ipl].plays;
    }

    // Choose a random number over the reverse weighted spectrum
    picked = ~~(Math.random() * reverseTotPlays);

    // Find which array member the random number belongs to
    for (ipl = 0;  ipl < myArray.length;  ++ipl) {
        revAcc += maxPlays - myArray[ipl].plays;
        if (revAcc > picked) {
            return ipl;
        }
    }
    return myArray.length - 1;
}

var pp = [{ plays: 3 }, { plays: 1 }, { plays: 2 }];
console.log(pickRandom(pp));

工作 JSFiddle Here

编辑:如果您不希望播放列表中播放次数最多的歌曲的概率为零,请在第一个循环后为 maxPlays 添加 +1。

【讨论】:

  • myArray[length].plays 到底是什么?
  • @Mark Reed,这是一个错字。谢谢!
  • 我喜欢这种方法!没有新的数组,循环保持相当短,没有变成无限的风险。最重要的是,这个过程是有意义的,当我摆弄它时,事情不会中断。谢谢!
【解决方案2】:

进行两步选择可能最简单,首先选择一首随机歌曲,然后查看该歌曲是否通过了旨在优先选择播放次数较少的歌曲的第二次测试。如果第二次测试失败,则丢弃它并重新开始整个过程​​。

一个例子(如果我犯了一个错误,请原谅我,我已经很久没有做任何javascript了):

function pickRandom(){
    var candidate;
    while (true){
        candidate = songs[Math.round(Math.random() * (songs.length - 1))];
        //largest_played is the largest number of plays of any one song
        // I've magiced it out of nowhere here, find it in a manner that
        // suits your program.
        if ( candidate.plays/largest_played < math.random() ){
            return candidate;
        }
    }
} 

显然,缺少很多光泽和错误检查,但这应该足以让您入门。

【讨论】:

  • 您的方法的执行时间是随机的,如果例程不够幸运,理论上可能是无限的。
  • @AresAvatar 非常正确。特别是因为这会使播放时间分布变平,从而降低选择任何一首歌曲的可能性。在 n 次循环后它应该有一个超时,它会退回到随机选择,但我不想弄乱逻辑。
猜你喜欢
  • 1970-01-01
  • 2013-11-21
  • 1970-01-01
  • 2010-12-04
  • 2011-05-01
  • 2022-07-06
  • 2020-03-14
  • 2016-01-26
  • 1970-01-01
相关资源
最近更新 更多