从数据集中随机抽取一定数量的数据
Posted 边城狂人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从数据集中随机抽取一定数量的数据相关的知识,希望对你有一定的参考价值。
今天翻到一个以前回答的问题:从列表中随机抽取一定数量的数据。之前回答是使用 Fisher-Yates 洗牌算法来解决的,但是阅读了评论之后,又有了一些新想法。
先不说是什么算法,只说说随机抽取的思路。
随机抽取的算法演进
假设有 n 个数据保存在一个列表 source
中(在 javascript 中是数组),需要随机抽取 m (m <= n) 个数据出来,结果放在另一个列表 result
中。由于随机抽取是一个重复过程,可以使用一个 m
次的循环来完成,循环体中每次从 source
中选一个数出来(找到它,并把它从 source
中删除),依次放在 result
中。用 JavaScript 来描述就是
function randomSelect(source, m)
const result = [];
for (let i = 0; i < m; i++)
const rIndex = ~~(Math.random() * source.length);
result.push(source[rIndex]);
source.splice(rIndex, 1);
return result;
在多数语言中,从列表中间删除一个数据,都会造成之后的数据重排,是个较低效率的操作。考虑到从一组数据中随机抽取一个是等概率事件,与数据所在的位置无关,我们可以把选出来的数据去掉之后,不减少列表长度,而是直接把列表最后一个数据挪过来。下一次随机取找位置的时候,不把最后一个元素考虑在内。这样改进之后的算法:
function randomSelect(source, m)
const result = [];
for (let i = 0, n = source.length; i < m; i++, n--)
// ^^^^^^^^^^^^^^^^^ ^^^
const rIndex = ~~(Math.random() * n);
result.push(source[rIndex]);
source[rIndex] = source[n - 1];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return result;
注意到这里 n--
和 n - 1
是可以合并的,合并后:
for (let i = 0, n = source.length; i < m; i++)
source[rIndex] = source[--n];
这时候,再次注意到,source
后面没用的空间,其实是和 result
空间一样大的,如果把这部分空间利用起来,就不再需要 result
。
function randomSelect(source, m)
for (let i = 0, n = source.length; i < m; i++)
const rIndex = ~~(Math.random() * n);
--n;
// 交换选中位置的数据和当前最后一个位置的数据
[source[rIndex], source[n]] = [source[n], source[rIndex]];
// 把后面 m 个数据返回出来就是随机选中的
return source.slice(-m); // -m 和 source.length - m 等效
如果保留原来的 result
及相关算法,会发现 result
和现在返回的数组元素正好是相反序排列的。但是不重要,因为我们的目的是随机选择,不管是否 revert,结果集都是随机的。
但是这么一来,假设 m = source.length
,整个 source
中的数据都被随机排列了 —— 这就是 Fisher-Yates 算法。当然,实际上只需要进行 source.length - 1
次处理就可以达到完全洗牌的效果。
以上是关于从数据集中随机抽取一定数量的数据的主要内容,如果未能解决你的问题,请参考以下文章