当前位置 : 主页 > 网络编程 > JavaScript >

详解JavaScript如何优雅地实现创建多维数组

来源:互联网 收集:自由互联 发布时间:2023-01-17
目录 前言 常规方法 for 遍历 map Array.from 不足 递归函数实现 链式函数实现 失败的尝试 处理办法 使用 Proxy 实现 前言 已经坚持力扣刷题 80 天了,其中经常需要创建多维数组 比如给你两
目录
  • 前言
  • 常规方法
    • for 遍历
    • map
    • Array.from
    • 不足
  • 递归函数实现
    • 链式函数实现
      • 失败的尝试
      • 处理办法
    • 使用 Proxy 实现

      前言

      已经坚持力扣刷题 80 天了,其中经常需要创建多维数组

      比如给你两个需求:

      • 创建一个 10 * 10 的数组,初值为 0
      • 创建一个 10 * 10 的数组,值为 0-99

      想知道掘友们会如何创建这两个数组呢?

      常规方法

      在这里展示一下常见的三种创建多维数组的方法

      for 遍历

      最经典的方法就是 for 循环

      下面是需求一的代码

      const arr = []
      for (let i = 0; i < 10; i++) {
        arr[i] = []
        for (let j = 0; j < 10; j++) {
          arr[i][j] = 0
        }
      }

      如果要实现需求二,只需将 arr[i][j] = 0 改为 arr[i][j] = i * 10 + j 就好了

      map

      我习惯使用 map 创建

      需求一

      const arr1 = new Array(10).fill(0).map(() => new Array(10).fill(0))

      需求二

      const arr2 = new Array(10).fill(0).map((_, i) => new Array(10).fill(0).map((_, j) => i * 10 + j))

      因为 map 不会处理空对象,所有创建数组后得先用 fill 赋值

      Array.from

      我看别人的题解经常看到用 Array.from 初始化数组的

      Array.from 能将一个类数组或可迭代对象转换成真实的数组,可以给第二个参数传递一个函数来修改新数组的元素,就像 map 一样

      关于类数组,只要是有 length 属性的对象,就被视为类数组

      需求一

      const arr1 = Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => 0))

      需求二

      const arr2 = Array.from({ length: 10 }, (_, i) => Array.from({ length: 10 }, (_, j) => i * 10 + j))

      不足

      以上的三种方法 for 遍历的代码太多不想用,map 和 Array.from 有时不宜阅读

      然而其他强类型语言声明多维数组都很方便,看起来也简单明了

      int arr[10][10]

      它们的 int 类型初值默认为 0,当然想实现需求二也得再做处理

      所以,我就想实现一个函数,简单明了地创建与设置多维数组的初值

      递归函数实现

      我们先明确函数的需求

      fun(0, false, 10) // 创建一个长度为10,初值为0的数组
      fun(0, true, 10, 10) // 创建一个10 * 10的数组,初值为0-99

      设计函数接收的参数

      1.数组的初值

      2.设置初值时是否需要递增

      3.剩余参数,表示每个维度的数组的长度

      代码也不难,直接展示了:

      如果是一维数组,就赋值,赋值时看一下要不要递增

      如果是多维,就递归创建,在需要递增时得计算下传入的初值

      function fun(value, inc, ...dimensions) {
        // 取首元素,创建数组
        const length = dimensions.shift()
        const arr = new Array(length)
        if (dimensions.length == 0) {
            // 一维数组 赋值
            for (let i = 0; i < length; i++) {
                arr[i] = inc ? value++ : value
            }
        } else {
            // 多维数组 递归创建
            let gap = dimensions.reduce((a, b) => a * b, 1) // 初值的间隔
            for (let i = 0; i < length; i++) {
                arr[i] = fun(value + (inc ? i * gap : 0), inc, ...dimensions)
            }
        }
        return arr
      }

      链式函数实现

      失败的尝试

      其实呢,个人对先传初值的方式略感不爽

      本来我是想创建一个链式的函数,和其他语言定义多维数组的语法一致并改造升级

      fun(10) // 创建一个长度为10,不设置初值
      fun(10)(10, 0) // 创建一个10 * 10的数组,初值为0
      fun(10)(10)(10, 0, true) // 创建一个10 * 10的数组,初值为0-999

      但是出现了一个问题,fun 的返回值是一个数组,但它又需要作为函数调用

      我尝试通过设置原型的方式让函数具有数组的功能

      Object.setPrototypeOf(fun, Array.prototype)

      但是函数自带不可配置的 length 属性,作为一个数组不能正确读取 length,那就无法正常使用

      处理办法

      要解决这个返回值的问题,那就只能根据参数进行区分,判断是返回函数还是返回数组

      • 当传入数字为正数时,视为长度,处理数组并返回函数自身
      • 当传入数字为非正数时,视为初值,将之前处理的数组赋值并返回

      在数组处理时用了一些技巧,将树形的数组结构铺平了,下面是代码

      let root = [] // 根数组 root[0] 将会是返回的结果
      let tile = [root] // 平铺数组
      
      // 函数实现
      function fun(length) {
          if (length <= 0) {
              // 结束标志 非正数
              try {
                  // 空属性会被JSON转换成null,然后再替换为初值
                  let res = JSON.stringify(root[0])
                  res = res.replaceAll('null', Math.abs(length))
                  return JSON.parse(res)
              } finally {
                  // 恢复变量
                  root = []
                  tile = [root]
              }
          } else {
              const next = [] // 新的平铺数组
              // 遍历平铺数组,里面的元素是倒数第二层
              for (const two of tile) {
                  // 首次调用two.length为0,但需要执行一次
                  for (let i = 0; i < (two.length || 1); i++) {
                      const one = new Array(length) // 最后一层
                      two[i] = one
                      next.push(one)
                  }
              }
              // 更替平铺数组
              tile = next
          }
          return fun
      }
      
      // 函数调用
      fun(10)(0) // [0,0,0,0,0,0,0,0,0,0]
      fun(2)(3)(-1) // [[1,1,1],[1,1,1]]
      fun(2)(2)(2)(-2) // [[[2,2],[2,2]],[[2,2],[2,2]]]

      如果想要实现初值递增,那就是再加一个参数或再加一种情况,留作读者自行实现了

      使用 Proxy 实现

      至此还不知足,我们可以借助 Proxy,将函数调用改为属性访问,更接近强类型语言的习惯

      实现一个 int 代理对象

      let root = []
      let tile = [root]
      // 拦截器
      const handers = {
          get(target, key) {
              key = parseInt(key)
              if (key <= 0) {
                  try {
                      let res = JSON.stringify(root[0])
                      res = res.replaceAll('null', Math.abs(key))
                      return JSON.parse(res)
                  } finally {
                      root = []
                      tile = [root]
                  }
              } else {
                  const next = []
                  for (const two of tile) {
                      for (let i = 0; i < (two.length || 1); i++) {
                          const one = new Array(key)
                          two[i] = one
                          next.push(one)
                      }
                  }
                  tile = next
              }
              return int
          },
      }
      // 用 proxy 创建 int
      const int = new Proxy({}, handers)

      至此,我们就可以像那些强类型语言一样,简单明了的定义多维数组了

      const arr = int[10][10][0] // 10*10 初值为 0 的数组

      到此这篇关于详解JavaScript如何优雅地实现创建多维数组的文章就介绍到这了,更多相关JavaScript多维数组内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!

      网友评论