随机获取数组中不重复的对象并返回

var arr = [
    {type: 'first', name: 'A'},
    {type: 'other', name: 'other_Z'},
    {type: 'first', name: 'B'},
    {type: 'other', name: 'other_x'},
    {type: 'first', name: 'C'},
    {type: 'other', name: 'other_y'}
]
阅读 2.3k
3 个回答
import _ from "lodash";

const arr = [
    { type: "first", name: "A" },
    { type: "other", name: "other_Z" },
    { type: "first", name: "B" },
    { type: "other", name: "other_x" },
    { type: "first", name: "C" },
    { type: "other", name: "other_y" }
];


function choose(arr, count) {
    // 先进行分组
    const { first, other } = _.groupBy(arr, "type");

    return [
        // 从 first 组里选 1 个
        first[~~(Math.random() * first.length)],
        // 再从 other 组里随机选 count - 1 个(先洗牌,再按顺序取)
        ..._.shuffle(other).slice(0, count - 1)
    ];
}

console.log(choose(arr, 3));

用 Lodash 写起来比较简单,不过这段程序还有一些优化空间:


补充,如果按另一种理解 ——

如果是随机选择任意项,仅要求第 1 项符合 type === "first" 的要求。那么不用分组,只需要先把 first 过滤出来,随机选 1 个,跟序号为 0 的元素交换位置;后面仍然按洗牌算法随机挨个选择,挨个往前放就好。

简单地说就是:仍然是使用洗牌算法来选择,只不过第 1 个选择比较特殊,单独处理

function choose(arr, count) {
    arr = [...arr];     // 产生一个新数组,避免修改到原数组

    // 在所有 type === "first" 的元素中选择一个,找到其序号
    const firsts = arr
        .map((it, index) => [it.type, index])
        .filter(([type]) => type === "first");
    const firstIndex = firsts[Math.random() * firsts.length | 0][1];

    // 把选出来的元素跟第 1 个元素交换位置
    [arr[0], arr[firstIndex]] = [arr[firstIndex], arr[0]];

    for (let i = 1; i < count; i++) {
        const randomIndex = Math.random() * (arr.length - i) | 0;
        [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
    }

    return arr.slice(0, count);
}

已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。
返回第一条数据的 type: 'first' 为必须条件

不太懂什么意思 按照我的理解写了

type: first 数据占比不算低的时候可以这么写

const getRandomItem = (arr, num) => {
  const items = []
  const indexes = {}
  while (num --) {
    let i
    do {
      i = (Math.random() * 1e6 | 0) % arr.length
    }
    while (indexes[i] || (! items.length && arr[i].type !== 'first')) 
    indexes[i] = true
    items.push(arr[i])
  }
  return items
}

type: first 较少时可以这么写提高效率

const getRandomItem = (arr, num, indexes = {}) => {
  const items = []
  while (num --) {
    let i
    do {
      i = (Math.random() * 1e6 | 0) % arr.length
    }
    while (indexes[i]) 
    indexes[i] = true
    items.push(arr[i])
  }
  return items
}

const getRandomItemWithFirst = (arr, num) => {
  const first = []
  for (const item of arr) {
    if (item.type === 'first') first.push(item)
  }
  const i_first = (Math.random() * 1e6 | 0) % first.length
  const indexes = { [i_first]: true }
  return [ arr[i_first] ].concat(getRandomItem(arr, num - 1, indexes))
}

当数据很小你只想链式一把梭的时候可以这么写

const getRandomItem = (arr, num) => Array(num)
  .fill()
  .map((_, i, self) => {
    while (true) {
      const v = arr[(Math.random() * 1e6 | 0) % arr.length]
      if ((v.type === 'first' || i) && ! self.includes(v)) return v
    }
  })

@边城 @ForkKILLET 我偷个懒咋儿样?

const total = 3; // 随机选出几条
// arr 是目标,具有6条记录
const rows = []; // 随机选出的记录
for(let i = 0, l = arr.length; i < l; i ++) {
    // 已经选够了
    if (rows.length === total) break; 
    // 如果随机到的数据数量低于目标数量,且与未遍历的记录数量一致,那么肯定入选
    if (arr.length - i - 1 === total - rows.length) {
        rows.push(arr[i]);
    } else if (Math.random() >= 0.5) {
        rows.push(arr[i]);
    }
};

至于第一条必选,将上面代码改成total是2, rows = [arr[0]],然后for循环从1开始就行了。

已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。

推荐问题
宣传栏