记一次nodejs内存溢出、前端页面崩溃

受到世界杯的影响,我想写一个程序,可以手动录入一场比赛所有盘口的赔率,胜平负3种情况、让球胜平负3种情况、比分26种情况、进球数8种情况、半全场9种情况,有的场次不能单买胜平负、让球胜平负,或者直接不开这种盘口,但相互之间串在一起不受限制。

以A、B、C、D为场次的标识,默认一种情况投入10块钱,算出能够赢的钱再排序。当只有一场比赛的时候,很好算。当有A、B两场时,场次本身的结果有A、B、AB,当有A、B、C、三场的时候,场次本身组合的结果就有A、B、C、AB、AC、BC、ABC。

首先要有得出涉及场次所有组合的算法,其中的规律在于,当处理的场次序号时i,i本身是一种组合,先把i之前的组合和i结合一遍,再把i放进去,依次类推,直到数组的最后一项,那么算法如下。

const teamList = ["A", "B", "C", "D"];
let teamCombinationList = [];
getTeamCombinationList(0);
function getTeamCombinationList(index) {
  const list = [];
  teamCombinationList.forEach((el) => {
    list.push([...el, teamList[index]]);
  });
  list.push([teamList[index]]);
  teamCombinationList = [...teamCombinationList, ...list];
  if (index < teamList.length - 1) {
    getTeamCombinationList(index + 1);
  }
}

结果如下

[
  [ 'A' ],
  [ 'A', 'B' ],
  [ 'B' ],
  [ 'A', 'C' ],
  [ 'A', 'B', 'C' ],
  [ 'B', 'C' ],
  [ 'C' ],
  [ 'A', 'D' ],
  [ 'A', 'B', 'D' ],
  [ 'B', 'D' ],
  [ 'A', 'C', 'D' ],
  [ 'A', 'B', 'C', 'D' ],
  [ 'B', 'C', 'D' ],
  [ 'C', 'D' ],
  [ 'D' ]
]

此时只需遍历teamCombinationList,有一个就是单场、有两个就是2串1、有三个就是3穿1,因为场次的个数不确定,现在需要一个通用算法实现类似多层for循环的结果。因为每个场次最多有51种结果,以A0、A1这种方式简单替代下。

[
    ["A0", "A1", "A2"],
    ["B3", "B4"],
],

举例,所有的结果是 'A0B3','A0B4', 'A1B3', 'A1B4', 'A2B3','A2B4'

算法核心逻辑是实现一个for循环,如果不是最后一个场次,则当前结果传给下一个递归,再进行for循环,如果是最后一个场次,则组合结果放进公共数组里。

/**
 * A  'A0','A1','A2'
 * B  'B3','B4'
 * C  'C0','C5'
 */
let oddResultList = [];
let teamCombinationList = [
  [["A0", "A1", "A2"]],
  [
    ["A0", "A1", "A2"],
    ["B3", "B4"],
  ],
  [["B3", "B4"]],
  [
    ["A0", "A1", "A2"],
    ["C0", "C5"],
  ],
  [
    ["A0", "A1", "A2"],
    ["B3", "B4"],
    ["C0", "C5"],
  ],
  [
    ["B3", "B4"],
    ["C0", "C5"],
  ],
  [["C0", "C5"]],
];

for (let i = 0; i < teamCombinationList.length; i++) {
  const list = teamCombinationList[i];
  againForEach([], 0);
  function againForEach(againList, index) {
    list[index].forEach((el) => {
      if (index === list.length - 1) {
        let str = "";
        [...againList, el].forEach((item) => {
          str += item;
        });
        oddResultList.push(str);
      } else {
        againForEach([...againList, el], index + 1);
      }
    });
  }
}

oddResultList 的结果如下

[
  'A0',     'A1',     'A2',     'A0B3',
  'A0B4',   'A1B3',   'A1B4',   'A2B3',
  'A2B4',   'B3',     'B4',     'A0C0',
  'A0C5',   'A1C0',   'A1C5',   'A2C0',
  'A2C5',   'A0B3C0', 'A0B3C5', 'A0B4C0',
  'A0B4C5', 'A1B3C0', 'A1B3C5', 'A1B4C0',
  'A1B4C5', 'A2B3C0', 'A2B3C5', 'A2B4C0',
  'A2B4C5', 'B3C0',   'B3C5',   'B4C0',
  'B4C5',   'C0',     'C5'
]

获得所有结果后进行排序,我一开始是调接口实现,后台基于nodejs实现的,当场次少于等于3场返回还是很快,但场次是4的时候,报错内存溢出,我以为是自己算法写的有问题,造成死循环,但是前面也没啥问题...

因为实际的数据结构比较复杂,我就使用--max-old-space-size命令加大,打开任务管理器,发现内存一致逐渐攀升道6、7千M的位置就不上去,加了几个输出,发现很快就算出结果,因为数组本身存储的是引用,其实这一块是不会内存溢出的,而是卡在了排序上,把排序代码去掉,还是不行,因为要把所有数据序列化后再返给前端,那么这个json是很大的。

其实所有数据都在前端的时候,是没必要再掉接口的,于是我把代码移到前端执行,4个场次虽然慢了些,但还是能展示,5个场次的时候也不行了,页面崩溃。

1个场次的时候最多51种,2个场次最多3015,3个场次最多166359,4个场次最多9150603,5个场次仅仅是计算处所有结果页面就崩溃了。至于优化的方式,尝试用简单的标记代替对象,等用到的时候再解析读取,然而一顿操作后还是不行,5个场次有503284374种结果,上亿级别了,我只是执行Array(503284374).fill(1)就崩溃了。

代码地址: https://github.com/xiaodun/sf-notes

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章