在Javascript中移动笛卡尔乘积迭代器

58wvjzkj  于 2023-01-16  发布在  Java
关注(0)|答案(2)|浏览(129)

使用像https://www.npmjs.com/package/cartesian-product-generator这样的库,我可以生成一个笛卡尔积迭代器,当我有4个数组,每个长度为100,给出1亿个组合时,这是必要的,,这对于生成一个数组来说太占用内存了。
然而,我想随机化这1亿个组合,这样我就可以以随机的顺序迭代所有的组合,我怎样才能随机化迭代的顺序而不把所有的1亿个组合都带入内存呢?

0g0grzrc

0g0grzrc1#

你可以使用下面的技巧。
考虑从元素到数字的Map。
以彼得斯为例:

// ----------  2    1    0 
const arr1 = ['r', 'g', 'b'];
const arr2 = ['early', 'late'];
const arr3 = ['high', 'low'];

您可以将arr1Map到(0,1,2),将arr2Map到(0,1),将arr3Map到(0,1)。
因此,像201这样的3位数将编码为r-晚-高。
但您可以将Map转换为十进制数,它是3 * 2 *= 12个组合,因此您的十进制数将需要2位数或适合4位("1001")。
这样,你的12个组合就可以通过生成数字0 - 11并对这些数字进行 Shuffle 来 Shuffle 。生成一个0 - 11的随机数,并使用位数组标记每个已观察/测试的组合。
在一个普通的例子中,每个数组的大小都是10,你可以直接用这个数字作为你的值的键,最后一个数字表示最后一个数组的元素,第一个数字表示第一个数组的第一个数字,依此类推。
处理不同的数组大小和10以下和10以上的数字需要更多的关注,但它是简单的乘法/除法和模运算。
例如,给定大小为212、7和19的3个数组,则有212 * 7 * 19 = 28196种可能的组合。
生成sample = rnd(28196)并得到13936,然后取该值模19(% arr3.length()),即9,因此选择第3个数组索引9处的元素。
你用13936/19得到733,733%7等于5,所以这表示arr2的第5个元素。
733/7 = 104,104%212为104,因此您从arr1中选取第104个元素。此操作可重复且快速。在位数组中,将索引13936处的位标记为true(默认值:假)。
我不是用JS而是用Scala写了一个类似的算法,它非常快。
如果你碰巧搜索了几乎整个数组,当数组的99. 999%已经标记为"真"时,搜索会变得很慢,重复的随机数总是返回一个已经测试过的值。那么你可能最好按顺序访问元素或对看到的元素进行计数,只从size-count中生成一个随机数,然后迭代位数组,跳过所有看到的位。你甚至可以从策略1开始,当事情变慢时切换到第二个。

xqkwcwgp

xqkwcwgp2#

你不能使用迭代器来随机化结果,因为你需要内存中的所有数组来随机化。
但是,您可以构建自己的随机笛卡尔积生成器:

function randomCartesianProduct() {
  return Object.keys(arguments).map(key => {
    let arr = arguments[key];
    return arr[Math.floor(Math.random() * arr.length)]
  });
}

const arr1 = ['r', 'g', 'b'];
const arr2 = ['early', 'late'];
const arr3 = ['high', 'low'];
for(let i = 1; i <= 50; i++) {
  let r = randomCartesianProduct(arr1, arr2, arr3);
  console.log(i + ': ' + r.join(', '));
}

更新1基于其他要求,以避免重复任何产品:

class RandomCartesianProduct {

  constructor(...args) {
    this.arrays = args;
    this.productSize = this.arrays.reduce((acc, arr) => acc * arr.length, 1);
    this.seen = {};
    this.seenSize = 0;
  }

  next() {
    if(this.seenSize >= this.productSize) {
      return null; // all combinations exhausted
    }
    let result;
    let hash;
    do {
      let idxes = [];
      result = this.arrays.map(arr => {
        let idx = Math.floor(Math.random() * arr.length);
        idxes.push(idx);
        return arr[idx];
      });
      hash = idxes.join('-');
    } while(this.seen[hash]);
    this.seen[hash] = true;
    this.seenSize++;
    return result;
  }
};

const arr1 = ['r', 'g', 'b'];
const arr2 = ['early', 'late'];
const arr3 = ['high', 'low'];
const random = new RandomCartesianProduct(arr1, arr2, arr3);
for(let i = 1; i <= 16; i++) {
  let r = random.next();
  console.log(i + ': ' + r);
}

输出:(多个随机输出中的一个)

1: g,late,low
2: g,late,high
3: g,early,low
4: r,early,low
5: b,early,low
6: r,early,high
7: b,early,high
8: b,late,low
9: g,early,high
10: b,late,high
11: r,late,low
12: r,late,high
13: null
14: null
15: null
16: null

一个this.seen散列跟踪已经返回的随机值。散列基于数组的随机索引,例如相对较短。
所有组合都用完后,返回null
请记住,在许多大的数组中运行这个函数会随着时间的推移而变慢,因为会有许多次重试,例如,对看到的项目的命中率会随着时间的推移而增加。

相关问题