使用 Mod 运算符对多个数组进行分页?

Using the Mod Operator for paginating multiple arrays?

我有许多数组需要根据请求的页面从中提取值。每个页面都需要分配一个唯一的数据组合,例如:

let numbers = ['One', 'Two']
let fruits  = ['Apples', 'Oranges']
let colours = ['Red', 'Green']
let names   = ['John', 'Jane']

None 个数组将永远为空,因此可用页面的数量将是所有数组的长度乘以彼此(因此在这种情况下,totalPages 将为 16

let totalPages = numbers.length * fruits.length * colours.length * names.length

对于请求的每个页面,我需要 return 一组唯一的索引,以便每个页面显示一组不同的值,并且每组中的值不超过一个。

我目前有嵌套的 for 循环(下面的示例),但我想知道是否有使用 mod 运算符或其他东西的更简洁的方法,这样我就不需要依赖 for 循环因为在某些时候我可能需要引入额外的数组而且我讨厌嵌套循环的样子...

let page = 4 // The requested page (not zero-based index)

let nPage = 1
for(let numberIndex = 0; numberIndex < numbers.length; numberIndex ++) {
    for(let fruitIndex = 0; fruitIndex < fruits.length; fruitIndex ++) {
        for(let colourIndex = 0; colourIndex < colours.length; colourIndex ++) {
            for(let nameIndex = 0; nameIndex < names.length; nameIndex ++) {
                // If the loop iteration matches the requested page,
                // return the combination of indexes as array
                if(page === nPage) {
                    return [numberIndex, fruitIndex, colourIndex, nameIndex]
                }
                nPage ++
            }
        }
    }
}

提前感谢任何 ideas/suggestions :o)

这真是一个 math/algorithm 问题(总是很有趣!)。所以一个好的方法是看看你是否能找出模式。例如,给定 3 个数组,[1,2,3,4][x,y,z][11,12],您期望

1 x 11
2 x 11
3 x 11
4 x 11
 
1 y 11
2 y 11
3 y 11
4 y 11
 
1 z 11
2 z 11
3 z 11
4 z 11
 
1 x 12
2 x 12
3 x 12
4 x 12
 
1 y 12
2 y 12
3 y 12
4 y 12
 
1 z 12
2 z 12
3 z 12
4 z 12

模式现在应该很清楚了,它只是在数数,就像你数数一样1,2,3,...9,10,11...99,100,101...。但在传统计数中,所有数字都从 0 开始并在 10 处换行。而在我们的示例中,第一个数字从 1 开始并在 4 处换行;第二个数字从 'x' 开始并在 'z' 处环绕,第三个数字从“11”开始并在 12 处环绕。

如果有人要求你找到 m-th 十进制数 4 位长的 n-th 位(从右边开始)(例如 1234 的第 3 位是 2),你可以截断temp = floor(m / 10 ** (n-1)),这会给你 1234、123、12、1。然后 temp % 10 会给出每个数字的最后一位:4、3、2、1。

在我们的例子中,我们的数字不是以 10 为底,因此我们用适当的计算替换 10:

let numbers = ['1', '2', '3'];
let fruits = ['a', 'b', 'c'];
let colours = ['x', 'y', 'z'];
let names = ['11', '12', '13'];

let getPage = i =>
    [
        numbers[i % numbers.length],
        fruits[Math.floor(i / numbers.length) % numbers.length],
        colours[Math.floor(i / numbers.length / fruits.length) % colours.length],
    ];
  
// print only the first 40 pages, otherwise Whosebug will truncate the console
for (let i = 0; i < 40; i++)
    console.log(getPage(i).join(' '));

如果您有一组动态数组(例如,您事先不知道将使用这些数组中的哪一个):

let numbers = ['1', '2', '3'];
let fruits = ['a', 'b', 'c'];
let colours = ['x', 'y', 'z'];
let names = ['11', '12', '13'];

let getPage = (pageI, arrays) => {
    // `pageCounts[i]` is the # of combinations that can generated from the 1st `i` arrays.
    let pageCounts = arrays.map((values, j) => arrays
        .filter((_, k) => k < j)
        .map(a => a.length)
        .reduce((a, b) => a * b, 1));
    return arrays.map((values, j) => values[Math.floor(pageI / pageCounts[j]) % values.length]);
};

// print only the first 40 pages, otherwise Whosebug will truncate the console
for (let i = 0; i < 40; i++)
    console.log(getPage(i, [numbers, fruits, colours, names]).join(' '));

我发现笛卡尔生成器在这里工作得非常好,因为它可以让您计算每个组合并在 n-th 值处停止,而无需提前计算。这是一个使用 this answer.

实现的示例

let numbers = ['One', 'Two']
let fruits  = ['Apples', 'Oranges']
let colours = ['Red', 'Green']
let names   = ['John', 'Jane']

let totalPages = numbers.length * fruits.length * colours.length * names.length

// Function from 
function* cartesian(head, ...tail) {
  let remainder = tail.length ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

function getPage(n) {
    const combinations = cartesian(numbers, fruits, colours, names);
    for (let i = 0; i < (n-1) % totalPages; i++) combinations.next();
    return combinations.next().value;
}

// loops back, so 1 is the same as 17
console.log(getPage(1))
console.log(getPage(17))


附带说明一下,我建议您使用数组而不是单独的变量来重构代码,因为它更易于维护。

const pages = [numbers, fruits, colours, names];

const totalPages = pages.length ? pages.reduce((a,b) => a * b.length, 1) : 0;

...

function getPage(n) {
  const combinations = cartesian(...pages);
  ...