【问题标题】:Get n non overlapping m-sized samples from an array从数组中获取 n 个不重叠的 m 大小样本
【发布时间】:2020-12-08 03:41:18
【问题描述】:

给定一个数组,我如何从中提取n 大小为m 的非重叠随机样本?

例如,给定数组:

const arr = [1, 2, 3, 4, 5, 6, 7, 8];

例如,调用sample(arr, 3, 2) 会返回[[7, 8], [4, 5], [2, 3]],调用sample(arr, 2, 4) 必然会返回[[1, 2, 3, 4], [5, 6, 7, 8],调用sample(arr, 5, 2) 会引发错误。

编辑 - 也许最初的问题并不清楚:样本应该是连续元素的列表。这就是为什么 sample(arr, 2, 4) 只能返回 [[1, 2, 3, 4], [5, 6, 7, 8] 而不能返回 [[2, 3, 1, 6], [5, 4, 7, 8],例如。

【问题讨论】:

  • 如果你这样做sample(arr, 20, 40)甚至sample(arr, 5, 2)等会发生什么?
  • @Dominik 应该会抛出一个错误,将进行编辑。
  • sample(arr, 2, 4) 也可以返回[[5,6,7,8], [1,2,3,4]] 吗?我认为它会在我的回答中,因为sample(arr, 3, 2) 返回[[7,8], [4,5], [2,3]],这是一个看似随机的样本顺序。
  • @3limin4t0r 是的,但这不是必要的步骤。最终的数组不需要洗牌。

标签: javascript arrays random sample


【解决方案1】:

您可以使用贪心算法,并从混洗后的数组中取出 m 大小的 n 个元组:

const arr = [2, 1, 3, 4, 5, 6, 7, 8];
function sample(arr, length, size){
  if(arr.length < length*size)
    throw new Error("too short");
  arr.sort(() => Math.random() - 0.5);
  let res = [];
  for(let i = 0; i < length; i++) res.push(arr.slice(i*size, i*size+size));
  return res;
}
console.log(sample(arr, 2, 4));

【讨论】:

  • 随机抽样在哪里?
  • @cabralpinto 添加了一行来洗牌
  • @cabralpinto 也带有“非重叠”,您的意思是您不会多次使用相同的值?
  • 这个解决方案只取前 n 个非重叠 m 大小的样本,而不是随机样本。
  • 是的,任何样本都不应包含与另一个样本相同的值。当然,除非数组有重复值。
【解决方案2】:

我认为最好的实现会先洗牌。这是我的两分钱:

function shuffle(array){
  let a = array.slice(), i = a.length, n, h;
  while(i){
    n = Math.floor(Math.random()*i--); h = a[i]; a[i] = a[n]; a[n] = h;
  }
  return a;
}
function sample(array, chunks, count){
  const r = [], a = shuffle(array);
  for(let n=0; n<chunks; n++){
    r.push(a.splice(0, count));
  }
  return r;
}
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(sample(arr, 3, 2)); console.log(sample(arr, 2, 4));

【讨论】:

    【解决方案3】:

    你可以先创建一个返回值格式的列表:

    [ 1,  2,  3,  4,  5,  6,  7,  8]
    [<---->, <---->, <---->, <>, <>] // sample(array, 3, 2)
    [<------------>, <------------>] // sample(array, 2, 4)
    

    这些格式数组可以使用长度写出:

    [1, 2, 3, 4, 5, 6, 7, 8]
    [   2,    2,    2, 1, 1] // sample(array, 3, 2)
    [         4,          4] // sample(array, 2, 4)
    

    然后打乱格式数组以获得随机样本选择:

    [1, 2, 3, 4, 5, 6, 7, 8]
    [   2, 1,    2,    2, 1] // sample(array, 3, 2)
    [         4,          4] // sample(array, 2, 4)
    

    然后对于格式数组的每个元素,从输入数组中删除第一个 n 元素。然后存储它们,除非它是填充物(放入一个大小的块以达到数组长度)。

    [1, 2, 3, 4, 5, 6, 7, 8]
    [[1,2], [4,5], [6,7]]  // sample(array, 3, 2)
    [[1,2,3,4], [5,6,7,8]] // sample(array, 2, 4)
    

    最后洗牌结果样本。

    [1, 2, 3, 4, 5, 6, 7, 8]
    [[4,5], [1,2], [6,7]]  // sample(array, 3, 2)
    [[5,6,7,8], [1,2,3,4]] // sample(array, 2, 4)
    

    const arr = [1, 2, 3, 4, 5, 6, 7, 8];
    console.log(sample(arr, 3, 2));
    console.log(sample(arr, 2, 4));
    console.log(sample(arr, 5, 2));
    
    function randomInt(limit) {
      return Math.floor(Math.random() * limit);
    }
    
    function shuffle(array) {
      for (let limit = array.length; limit > 0; --limit)
        array.push(...array.splice(randomInt(limit), 1));
    }
    
    function sample(array, sampleCount, sampleLength) {
      let elementCount = sampleCount * sampleLength;
      if (elementCount > array.length)
        throw "invalid sampleCount/sampleLength arguments";
        
      const filler = {valueOf: () => 1};
      const fillerCount = array.length - elementCount;
      const lengths = Array.from(
        {length: sampleCount + fillerCount},
        (_, i) => i < sampleCount ? sampleLength : filler
      );
    
      shuffle(lengths);
      const samples = Array.from(array);
      for (const length of lengths) {
        const sample = samples.splice(0, length);
        if (length === filler) continue;
        samples.push(sample);
      }
      shuffle(samples);
      
      return samples;
    }

    注意===length === filler 中很重要。如果您使用==filler 也将等于1。这将与像 sample(array, 5, 1) 这样的调用发生冲突,其中每个样本长度都是 1

    const filler = {valueOf: () => 1};
    
    console.log("1 == filler       //=>", 1 == filler);
    console.log("2 == filler       //=>", 2 == filler);
    console.log("filler == filler  //=>", filler == filler);
    console.log("1 === filler      //=>", 1 === filler);
    console.log("2 === filler      //=>", 2 === filler);
    console.log("filler === filler //=>", filler == filler);

    【讨论】:

    • 真的很喜欢这种方法。还学习了{valueOf: () =&gt; 1} 技巧,这很有用。谢谢你的回答。
    • 如果您想创建自己的“复杂”号码,valueOf 很有用。当需要一个数字时,它会自动调用。例如4 + complexObject 会调用valueOf 函数来检索一个值。
    【解决方案4】:

    您可以使用Rando.js(加密安全)、mapsplice 轻松完成此操作。只需使用 randojs 的 randoSequence 函数来打乱提供的数组并将n size-m 数组从打乱的数组中拼接出来,以获得我们需要返回的所有内容。如果提供的数组的值太少,我们返回的后面的数组就会更短。

    function sample(arr, n, m){
      arr = randoSequence(arr).map(i => i.value), sample = [];
      for(var i = 0; i < n; i++) sample[i] = arr.splice(-m);
      return sample;
    }
    
    console.log(sample([1, 2, 3, 4, 5, 6, 7, 8], 3, 2));
    &lt;script src="https://randojs.com/2.0.0.js"&gt;&lt;/script&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-25
      • 2022-01-17
      • 1970-01-01
      • 2012-11-25
      • 2018-12-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多