我有三个选项,需要随机选择一个(在 JavaScript 中)。但不是完全随机的。我希望第一个选项有 10% 的几率,第二个选项有 20% 的几率,第三个选项有 70% 的几率。
以下是一些实现方法。
假设数据如下
var choices = [
[10, "apples"],
[20, "oranges"],
[70, "bananas"]
];
此外,我们还为迭代器和随机数等内容设置了作用域变量。我们不妨将这段代码视为概念性代码,而非生产代码。
在最小值和最大值之间测试
我首先想到的是生成一个 1-100 之间的随机数,然后测试该数字是否在…
- 1-10 之间(苹果)
- 11-30 之间(橙子)
- 31-100 之间(香蕉)
获取这些最小值和最大值有点冗长。您运行一个循环来遍历所有选项。第一个选项的最小值为零,其余选项的最小值为所有先前选项的总和。最大值为当前选项和所有先前选项的总和。
在循环的每次迭代中,您都会测试随机数是否在刚刚生成的范围内。如果是,则设置最终选项并中断。否则,执行另一次迭代。
function pickChoice() {
rand = Math.floor(Math.random() * 100);
choice = -1;
for (i = 0; i < choices.length; i++) {
// set up min
if (i === 0) {
min = 0;
} else {
min = 0;
// add up all the values so far
for (i2 = 0; i2 < i; i2++) {
min += choices[i2][0];
}
// one higher
min++;
}
// set up max
if (i === 0) {
max = choices[i][0];
} else {
max = 0;
// add up all the values so far
for (i2 = 0; i2 < i + 1; i2++) {
max += choices[i2][0];
}
}
if (rand >= min && rand <= max) {
choice = i;
break;
}
}
// If the choice is still -1 here, something went wrong...
};
查看 CodePen 上 Chris Coyier (@chriscoyier) 的笔 数组项目的百分比几率。
测试是否高于最小值
David DeSandro 有一个更巧妙的想法(当然)。从一开始就为每个选项指定一个最小值。每个选项的最小值为所有先前选项的总和。因此数据变为
- 苹果:0
- 橙子:10
- 香蕉:30
然后生成一个随机数,遍历选项,如果随机数高于最小值,则将选择设置为该选项。在这个循环中,选择可能会被设置多次,但最终会选择正确的选项。
function pickChoice() {
var rand = Math.random() * 100, pick, choice, i;
// go through choices, update pick if rand is above bottom percent threshold
for ( i = 0, len = choices.length; i < len; i++ ) {
choice = choices[i];
if ( rand > choice.percent ) {
pick = choice;
}
}
return pick;
}
David 还根据其分支中的 HTML 中的值设置数据。
查看 CodePen 上 David DeSandro (@desandro) 的笔 数组项目的百分比几率。
构建一个包含所有可能性的新数组
George Papadakis 使用另一种方法制作了一个快速演示。构建一个新数组,其中每个选项都按比例正确的次数表示。因此,根据我们的数据,可以简化为
- 新数组中 1 个项目是苹果
- 新数组中 2 个项目是橙子
- 新数组中 7 个项目是香蕉
然后,当您生成一个随机数来选择一个项目时,只需从新数组中直接选择该项目,例如 choices[rand]。
// quick'n'dirty
NodeList.prototype.map = Array.prototype.map;
var choices = [],
lis = document.querySelectorAll('li')
.map(function (item) {
var percent = parseFloat(item.querySelector('span').textContent) / 10,
label = item.querySelector('strong').textContent;
for (var i = 0; i < percent; i++) {
choices.push(label)
}
});
function pick() {
var rnd = parseInt(Math.random() * 10);
document.querySelector('div.choice').innerHTML = choices[rnd];
}
查看 CodePen 上 George Papadakis (@phaistonian) 的笔 数组项目的百分比几率。
更多?
您可能可以将所有这些想法混合到其他解决方案中。或者用完全不同的方法来解决这个问题。看到同一个问题的不同解决方案总是很有趣的!
我的实际用例是需要随机化一些广告,但需要基于百分比份额而不是简单的随机拆分。任何一种方法都可以做到这一点。
以下是一些实现方法。
应该是 -
这里有一些实现方法。
我不确定“here’re”是否是一个正确的缩写?我想我会使用“here are”。谢谢!我将隐藏此线程,因为它与文章内容相关。
标题中的第一个不定冠词不正确。应该是“a”而不是“an”:“根据范围选择随机选项”(“Random”中的“R”不发音,因此前面应该使用“a”。)
无论如何,感谢您的文章。
是的,这是多次编辑标题造成的产物。我将将其更改为“a”,然后隐藏此线程,因为它对阅读评论以获取与内容相关信息的人们来说用处不大。
哇,前两条评论都是语法纠正!我只是想对这篇文章发表一句积极的评论。感谢您提供各种实现方法的示例。我不确定实际应用是什么,但当您解释了您的推理时,我明白了。好文章!
嗨
为您制作了另一个版本:-)
带注释
它经过了一些优化,因为我喜欢这样做 - 81 个字符
我正要回复相同的基本结果。除了我要进行两处更改。首先,不要要求用户使其比例加起来等于 100,只需计算总和即可。这样,1、2、7 的权重与 10、20、70 甚至 1000、2000、7000 相同。我还将对数组进行排序,因为也不能保证用户也进行了排序。我的结果是
继续改进性能方面的内容。
http://jsperf.com/pick-from-choices
请注意,由于浮点数并非完全精确,因此我强制使用了整数。速度也略有提升。我还制作了一个使用 Int32Array 的版本,它为 Chrome 提供了相当不错的提升
否则,我不知道 Chrome 最近发生了什么,因为 Firefox 在其他领域的速度上碾压了 Chrome。
另外,这两个版本也适用于 Internet Explorer。
@Vesa;在 IE11 中不起作用:/
感谢您提供 jsPerf!
@Tom 是的,imma 的代码失败了,但 jsperf 将继续在我的代码上方运行巨大的错误消息。我不知道为什么 jsperf 没有正确检测到 IE11,它将其归类为“其他”。
对于这个问题,我更喜欢使用几率而不是百分比。换句话说,不要强制总和等于 100。这样,我就可以轻松地向数组中添加另一个元素,而无需调整所有百分比。如果我想要它们都具有相同的可能性,我只需为每个元素输入“100”。如果我想要一个元素的可能性是两倍,我会输入 200。我发现这种方法更健壮,而且同样易于使用。
我采用了您关于几率的想法,但我想要一个性能更高、更通用的函数。
一种使用 lodash 的方法
笔以供更多解释
前两个版本有点问题。
范围不正确,应该是
– 0-9(苹果)
– 10-29(橙子)
– 30-99(香蕉)
因为
Math.random() * 100得到的结果是一个小于100的数字,并且可能为0。所以你的百分比会稍微失真。
(你的第一个算法选择以下选项:
Math.random()选择
0-10苹果
11-30橙子
31-99香蕉
第一个选项被选择的频率略高,最后一个选项被选择的频率略低。)
在第二个版本中,甚至有可能没有设置任何选择(如果
Math.random()返回正好为0)。这是一个时间复杂度为 O(n) 的解决方案,不需要总和为 100(或 1 或其他值)。你不需要预先计算任何东西,并且项目可以按任意顺序排列。在这个例子中,“苹果”会被选中的概率为 1/6,“橙子”为 2/6,“香蕉”为 3/6。
我喜欢这种方法。
它依赖于将布尔值转换为数字。如果我们的随机数小于等于 0.1,它将不通过任何测试,因此索引为 0;如果它在 0.1 到 0.3 之间,它将通过一个测试,因此索引为 1;如果它大于 0.3,它将通过两个测试,因此索引为 2。
如此优雅简洁,仅仅三行代码。太棒了!
为这个点赞。比我下面的方法更好,没想到这一点。做得好。布尔值方法是我将来可以考虑使用的方法。谢谢。
谢谢。我首先承认这是一种相当快速且粗略的方法,但当你只有几个项目并且不需要一个完整的子程序来进行随机选择时,它就足够好了。
我认为有一个更简单的方法,抱歉,它使用的是 Google 电子表格格式,今天太忙了……
1* 用于将 true 或 false 转换为 1 或 0。
保留不必要的括号以方便理解。
可能的结果是 1、2 或 3。
只需减去 1 即可获得从 0 开始的 Javascript 表格索引。
抱歉,A1 当然是随机数,即在 A1 单元格中输入 =rand()。
CSSConfAU 2015 新闻,你可能可以偷偷联系我们的其中一位代表http://goo.gl/9vIsH2,看看他学到了什么。
if (i === 0) {
min = 0;
} else {
min = 0;
相反,我建议…
min = 0;
min += (i > 0) ? 1 : 0;
另一种方法,可能与上面的一些方法类似,但它运行良好。
只需设置数组中的最大值。实际上,通常不需要进行 100 次随机选择,除非你有一套更复杂的信息。即使这样,我们也可能可以更经济地做到,但对于粗略的尝试,1-10 也同样有效。根据复杂程度增加或减少数字。
最后的想法。为了可重用性,我会取上面我编写的函数,从中剥离数组。将数组作为参数传递给函数。然后进行一些初始检查,以确保:1. 它是一个我们正在寻找的正确的多维数组,2. 它按 [n][0] 从低到高排序。如果不是从低到高,则将其排序为从低到高。然后,根据最高值使用 math.random。
如果我们可以使用 lodash,这里有一个例子
— 生成一个数组,其中包含每个项目的指定数量的选择 /
— 测试一下
虽然 lodash 很好用,但我发现这种方法更易读
在一快步中生成大型数组,并且通过
Math.random()选择随机项比_.sample更清晰,因为“sample”直到我阅读文档才明白它真正的含义。我认为像 lodash 这样的库的缺点之一是它们拥有大量的实用程序函数,因为总是有你从未听说过的新的函数,即使你已经使用该库几个月了。而且你会随着时间的推移忘记那些很少需要的函数。我注意到的另一件事是,函数式编程的支持者真的想避免像
while或switch这样的简单过程代码,即使函数式解决方案没有意义。我不确定哪种方法更好;如果你能理解函数式方法,那么你可能没问题。我个人更喜欢更易于人类阅读的代码;毕竟处理器可以非常快地运行,因此障碍在于人类对代码的理解,而不是处理器的理解。鉴于此,这两种方法都生成了一个非常浪费的数组——考虑如果存在 1,000,000 个选择而不是 100 会发生什么。
但是,我更倾向于一种尽可能提前进行计算的方法,以便尽可能快地选择项目。
当有意义时,我更喜欢过程式方法,因为它限制了代码量,从而限制了出错的可能性。
但我认为我们正在偏离最初的问题,即如何从有限的选择集中选择广告,并对这些选择进行加权。任何快速且粗略地完成此操作的方法都可以封装在函数中,你就可以做得很好。
我发现大多数函数式代码与任何其他代码一样,并没有更易于人类阅读。我认为函数式范式可以提供的最大价值是拥有只做一件事的简单函数的想法。这远比函数式范式本身更有价值,因为你可以将其应用于你所做的任何编码。
一旦你添加了像 lodash 这样的库,将大量简单函数收集到长长的单行代码中,你又回到了难以阅读代码的问题,因为这些行是大量逻辑的紧凑丛林。阅读顺序也与大多数“常规代码”不同,因为它通常必须从后往前阅读(从最里面的函数调用到最外面的函数调用),这与典型的自上而下、逐行阅读的顺序相冲突。