【问题标题】:Node - Access last index of multidimensional arrays节点 - 访问多维数组的最后一个索引
【发布时间】:2021-04-19 04:59:59
【问题描述】:

我有一个我认为很简单的问题,但我看到了相互矛盾的答案。

假设我有以下数据结构:

const data = [
  ...,
  {
    someKey: [
      ...,
      {
        someDeepKey: [
          ...,
          'two'
        ],
      }
    ]
  }
]

因此,访问以下所有内容的第一个索引将如下所示:

data[0].someKey[0].someDeepKey[0]

但是如果我想找到所有这些的最后一个索引,我还没有找到一个干净的方法。我看到有人建议扩展 Array 原型本身,我觉得这是个坏主意。

我能想到的唯一其他“干净”解决方案是链接变量以使其半可读。

let someKey = data[data.length - 1].someKey
let someDeepKey = someKey[someKey.length - 1].someDeepKey
let someDeepKeyValue = someDeepKey[someDeepKey.length - 1]

有人可以建议一种更清洁的方法吗?我相信 Python 支持一些简洁的东西,例如:

data[-1].someKey[-1].someDeepKey[-1]

我想知道 JS 中是否有类似的东西。

【问题讨论】:

  • 据我所知,目前没有简单的方法可以做到这一点,但您可能有兴趣查看this proposal of ECMAScript。它目前处于第 3 阶段,我猜很快就会标准化。

标签: javascript node.js arrays


【解决方案1】:

我确实偶然发现了这一点。它似乎可以在不改变原始数组的情况下做我想做的事,比做整个.length - 1 链要干净得多:

let data = [
  {
    someKey: [
      {
        someOtherKey: [
          'Fake',
          'Name'
        ]
      }
    ]
  }
];

let name = data.slice(-1).pop()
  .someKey.slice(-1).pop()
  .someOtherKey.slice(-1).pop();

console.log(name) // 'Name'
console.log(data) // Original data

不确定它的性能如何,但它肯定至少更具可读性,并且可以获取所需的最终值。

这可能是一场性能噩梦,因为它在内存中制作了如此多的副本,但当我们只处理较小的数据集时,我认为这符合在深度嵌套数组中获取准确值而不改变任何内容的要求。


进行了一些评估。这里有一些基本的测试:

// Scaffold a dataset
let dataSet = [];
for (let i = 0; i < 100; i++) {
  let someObj = {
    someKey: []
  }
  for (let j = 0; j < 100; j++) {
    let someOtherObj = {
      someOtherKey: []
    }
    for (let k = 0; k < 1000; k++) {
      someOtherObj.someOtherKey.push(k)
    }
    someObj.someKey.push(someOtherObj)
  }
  dataSet.push(someObj)
}

const slicePop = (data) => {
  // Slice Pop
  let start = window.performance.now();
  let name = data.slice(-1).pop()
    .someKey.slice(-1).pop()
    .someOtherKey.slice(-1).pop();
  let end = window.performance.now();
  let time = end - start;
  return time
}

const spreadPop = (data) => {
  // Spread Pop
  let start = window.performance.now();
  let name = [...[...[ ...data ].pop().someKey].pop().someOtherKey].pop()
  let end = window.performance.now();
  let time = end - start;
  return time
}

const lastMethod = (data) => {
  // Function
  let start = window.performance.now();
  const last = (arr) => arr[arr.length - 1]
  let name = last(last(last(data).someKey).someOtherKey)
  let end = window.performance.now();
  let time = end - start;
  return time
}

const variables = (data) => {
  let start = window.performance.now();
  let someKey = data[data.length - 1].someKey
  let someOtherKey = someKey[someKey.length - 1].someOtherKey
  let name = someOtherKey[someOtherKey.length - 1]
  let end = window.performance.now();
  let time = end - start;
  return time
}

const lastDeep = (arr) => {
  let start = window.performance.now();
  let name = ['someKey', 'someOtherKey', ''].reduce(
    (last, cur) => (
      (res = last[last.length - 1]),
      res[cur] ? res[cur] : undefined
    ),arr)
  let end = window.performance.now();
  let time = end - start;
  return time
}

let slicePopTimes = [];
let spreadPopTimes = [];
let lastMethodTimes = [];
let variablesTimes = [];
let reduceTimes = [];

for (let i = 0; i < 100; i++) {
  slicePopTimes.push(slicePop(dataSet))
}

for (let i = 0; i < 100; i++) {
  spreadPopTimes.push(spreadPop(dataSet))
}

for (let i = 0; i < 100; i++) {
  lastMethodTimes.push(lastMethod(dataSet))
}

for (let i = 0; i < 100; i++) {
  variablesTimes.push(variables(dataSet))
}

for (let i = 0; i < 100; i++) {
  reduceTimes.push(lastDeep(dataSet))
}

const getAvg = (arr) => {
    let total = arr.reduce((acc, i) => {
        acc += i
    return acc
    }, 0);
    return ((total / arr.length) * 1000)
}

let results = {
  slicePopTime: getAvg(slicePopTimes),
  spreadPopTime: getAvg(spreadPopTimes),
  lastMethodTime: getAvg(lastMethodTimes),
  variablesTime: getAvg(variablesTimes),
  reduceTime: getAvg(reduceTimes)
}
console.log('RESULTS', results)

【讨论】:

    【解决方案2】:

    您可以关注此代码

    let x = [...[...[ ...data ].pop().someKey].pop().someDeepKey].pop()
    
    

    【讨论】:

    • 虽然我没有提到,但这确实会改变原始数据,对吗?我认为这是我想避免的副作用。
    • 哦,我明白了,因为你正在传播它,所以你也在内存中制作副本。
    • 我认为如果 yourArray 有大量元素,这将是一个昂贵的操作,但我认为它很干净,因为你想要一种干净的方式:)
    • 你不觉得我偶然发现的答案更高效,更易读吗?因为在那种情况下,它实际上只是将切片的末端保存在内存中,而不是将整个东西分散开来?
    • 有趣的是,将两个操作平均运行超过 10,000,000 个索引 100 次表明您的速度大约是两倍。
    【解决方案3】:

    定义一个名为 last 的函数来获取数组的最后一个元素并像这样使用它last(last(last(data).someKey).someDeepKey)

    【讨论】:

      【解决方案4】:

      这里使用reduce 的小工具非常方便,适用于任何级别的嵌套。

      const data = [
        {
          someKey: [
            {
              someDeepKey: ["one"],
            },
          ],
        },
        {
          someKey: [
            {
              someDeepKey: ["one", "two"],
            },
          ],
        },
      ];
      
      const lastDeep = ["someKey", "someDeepKey", ""].reduce(
        (last, cur) => ((res = last[last.length - 1]), res[cur] ?? res),
        data
      );
      
      console.log(lastDeep);

      【讨论】:

      • 我永远听不懂reduce
      • 我认为这是一个聪明的解决方案,但它像糖蜜一样缓慢。对于 100 * 100 * 1000.... 的多维数组,查找时间为: lastMethodTime: 0.749sec reduceTime: 1.349sec slicePopTime: 1.050sec spreadPopTime: 0.949sec variablesTime: 0.549sec
      猜你喜欢
      • 2014-09-27
      • 2019-11-20
      • 1970-01-01
      • 2012-11-03
      • 1970-01-01
      • 2015-02-01
      • 2021-12-27
      • 2016-11-25
      • 2020-07-19
      相关资源
      最近更新 更多