一个关于JS解决数组相乘问题

5,249 阅读5分钟

数组相乘,顾名思义就是将多个数组的每一元素乘(组合)起来。它的结果以几何数级增长,初次遇到此类问题时,常常使人头皮发麻,我们现在以js的角度来解决这个问题。

从实例出发

众所周知,女孩出门前一般需要经过精心打扮,那么假设你有一个女朋友,她有着3顶帽子,5件衣服,5条裤子,3双鞋子,2只口红,4个包包,2副墨镜,且心情可能会影响穿着,她可能因为心情不好而选择不带一些物品,但是她会告诉你她会穿戴什么,要求列举所有方案,看到题目后……

不说了,先将实际问题转化成语言问题吧。
七个数组,分别表示七种穿戴,数组中存值为该穿戴的代号,传值为字符串是她告诉你她需要的穿着,例"clothes trousers",中间以空格隔开

const hat = ['a','b','c'];
const clothes = ['0','1','2','3','4'];
const trousers = ['q','w','e','r','t'];
const shoes = ['A','B','C'];
const lipstick = ['$1','$2'];
const bag = ['¥1','¥2','¥3','¥4'];
const sunglasses = ['^1','^2'];
function getComb(str){
    return arr;
}

解决思路

秉着不管什么问题,看到数组我就用循环的思路,去解题,难免会遇上很多问题。如果,题目上明确指出女朋友心情必须max每次出门都装备拉满,那ok没问题,7次循环解决。但是女人心海底针呐,你只能通过她告诉的穿着来列举(如果无论什么情况,你都用7次循环,那当我没说)。

随机数法

我们可以让电脑自己随机组合,并判断如果已经添加过这个结果,就不加进数组就ok了。

const arr = str.split(' ')
    .map(name => {
        switch (name) {
            case 'hat': return hat;
            case 'clothes': return clothes;
            case 'trousers': return trousers;
            case 'shoes': return shoes;
            case 'lipstick': return lipstick;
            case 'bag': return bag;
            case 'sunglasses': return sunglasses;
        }
    })

先将传入的字符串转化一个与它传值相关的二维数组,例:传入"hat clothes"
arr为[ [ 'a', 'b', 'c' ], [ '0', '1', '2', '3', '4' ] ],这样我们就得知了穿戴的数目,以及该穿戴的种类了。我们不难得出,这种情况下最多能列出15种结果,定义一个total变量并修改一下上面代码。

let total = 1;
const arr = str.split(' ')
    .map(name => {
        switch (name) {
            case 'hat': total *= hat.length ; return hat;
            case 'clothes': total *= clothes.length ; return clothes;
            case 'trousers': total *= trousers.length ; return trousers;
            case 'shoes': total *= shoes.length ; return shoes;
            case 'lipstick': total *= lipstick.length ; return lipstick;
            case 'bag': total *= bag.length ; return bag;
            case 'sunglasses': total *= sunglasses.length ; return sunglasses;
        }
    })

如果使用随机数我们就能将一个多次循环转化为一个while判断的for循环。我们new一个数组result,由于我们知道一共有几种结果,所以while循环的条件就是:result.length < total.

let result = [], sum = '';
    while (result.length < total) {
        for (let i = 0; i < arr.length; i++) {
            sum += arr[i][parseInt(Math.random() * arr[i].length)];
        }
        if (result.indexOf(sum) == -1)
            result.push(sum);
        sum = '';
    }
    return result;

注意:Math.random()是产生一个[0,1)的随机浮点数,数组的索引是整数,所以我们需要转换类型;sum是数组的组合,所以我们在添加完后需要了给它还原.

随机数法的缺点:
算法过于暴力,由于Math.random值返回的不确定性,导致很多时间会浪费在生成一个数组已经添加过的值上。

reduce方法

我们也可以使用reduce方法非常简便的完成数组的乘法.很多初学者可能会对reduce比较陌生,因为这是一个不常见的方法,但是使用reduce进行很方便,先简单介绍一下reduce方法。

reduce方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值

reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。

arr.reduce(callback,[initialValue])
callback (执行数组中每个值的函数,包含四个参数)
previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
currentValue (数组中当前被处理的元素)
index (当前元素在数组中的索引)
array (调用 reduce 的数组)
initialValue (作为第一次调用 callback 的第一个参数。)

基础用法

 const a = [1,2,3,4,5,6];
    const rst = a.reduce((pre,cur)=>{
        return pre + cur;
    })
    console.log(rst); // rst = 1+2+3+4+5+6

pre为第一个值或上次计算的结果,这里没有传初值,pre初始默认为0,cur为数组的每一个值。这里解析过程:

=> pre = 0 ; cur = a[0];
=> pre = 0 + a[0] ; cur = a[1];
=> pre = 0 + a[0] + a[1] ; cur = a[2];
...
rst = 0 + 1 + 2 + 3 + 4 + 5 + 6 // 21

用reduce解决数组相乘

return arr.reduce((pre,cur)=>{
        return [].concat(...pre.map(e=>cur.map(ele=>ele+e)))
    }) 

例:

str = 'clothes hat bag';
=> arr :
[ [ '0', '1', '2', '3', '4' ],
  [ 'a', 'b', 'c' ],
  [ '¥1', '¥2', '¥3', '¥4' ] ]

首先将一个复杂组合问题,转化为叠加组合问题。如果能将arr[0],arr[1]两个组合的结果返回给pre,那么我们就能通过两两组合来完成复杂组合。接下来使用map完成两两组合:

pre : [ '0', '1', '2', '3', '4' ]
cur : [ 'a', 'b', 'c' ]
pre.map(e => cur.map(ele => e + ele));
/*第一次*/
e : '0' , ele: 'a' , e + ele ='0a', pre[['0a']]
e : '0' , ele: 'b' , e + ele = '0b', pre[['0a','0b']]
...
/*结束*/
pre[['0a','0b','0c'],['1a','1b','1c'],['2a','2b','2c'],['3a','3b','3c'],['4a','4b','4c' ]]
cur : [ '¥1', '¥2', '¥3', '¥4' ]

接下来我们只需要将二维数组pre转化为一维数组,然后函数就会随着reduce一步一步将数组相乘起来了

[].concat[...pre.map(e => cur.map(ele => e + ele))]

扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,一般在传参时使用。

到这里我们就解决这个女朋友出门的问题了。