【问题标题】:Deep key structure based on recursion基于递归的深度密钥结构
【发布时间】:2018-04-02 12:22:37
【问题描述】:

我使用 lodash 已经有一段时间了,我真的很喜欢 _.set_.get 方法。

我正在尝试解决一个问题,以获取最终值为字符串的深层键路径,但是当我对它太愚蠢时。花了 3 个小时,却找不到完美的解决方案:

const myObject = {
  a: 'myObject.a',
  b: {
    ba: 'myObject.b.ba',
    bb: ['myObject.b.bb[0]'],
  },
  c: [
    { ca: 'myObject.c[0].ca' },
  ],
};

所以我有myObject(这在现实生活中更加嵌套)并且我想获得值的路径,但只有最后一个。

该方法看起来像getDeepPaths(myObject),在这种情况下会返回:['myObject.a', 'myObject.b.ba', 'myObject.b.bb[0]', 'myObject.c[0].ca' ]

以前有人解决过这样的问题吗?

【问题讨论】:

  • 我已经解决了类似的问题,但必须挖掘它,我现在迟到了。但提示:您将不得不使用递归。而不是.a 尝试['a']。为常见模式编写算法更简单。
  • 感谢@Rajesh 的回复和提示。我一走路就试试。递归是一种让我感觉要爆炸的东西。

标签: javascript recursion lodash


【解决方案1】:

递归其实没那么难。以下是解决此问题的方法:

const myObject = {
  a: 'myObject.a',
  b: {
    ba: 'myObject.b.ba',
    bb: ['myObject.b.bb[0]'],
  },
  c: [
    { ca: 'myObject.c[0].ca' },
  ],
};


var stringLeaves = function(path, obj) {

  if (typeof obj === 'string') {
    return [path]
  }

  return Object.keys(obj)
          .filter(k => obj.hasOwnProperty(k))
          .map(k => stringLeaves(path + '.' + k, obj[k]))
          .reduce((a,x) => a.concat(x), []); // this line flattens the array
};

console.log(stringLeaves('myObject', myObject));

这项工作由stringLeaves 函数完成。在这个函数中:

  • 如果作为参数传入的obj是字符串,则返回当前路径。
  • 否则我们假设该对象是一个数组或一个通用对象,在这种情况下我们会遍历它的属性:
    • 对于每个属性,递归调用stringLeaves,方法是传入调整后的路径(当前路径 + 新属性名称)和驻留在该特定键处的对象/值。

该函数的约定是它返回一个包含所有可能匹配项的数组。这就是为什么:

  • 对于标量字符串值,我返回一个数组(以保持一致)
  • 我有 .reduce((a,x) => a.concat(x), []); 行:将一组数组转换为一个包含原始数组中所有值的数组。

请注意,该函数无法推断出您的对象称为myObject,因此我将该名称作为初始路径传递。

【讨论】:

  • 这真的很棒。我已经设法想出了我自己的解决方案,但它只是你所写内容的不那么优雅的版本。非常感谢!
【解决方案2】:

我将提供一个更通用的解决方案,不使用 lodash 或其他外部依赖项

const traverse = function* (node, path = [])
{
  if (Object (node) === node)
    for (const [ key, value ] of Object.entries (node))
      yield* traverse (value, [ ...path, key ])
  else
    yield [ path, node ]
}

我们可以使用for 循环轻松遍历我们的数据。请注意,生成器为原始对象中的每个值生成一个 path-value 对。 所有原始值都包含在输出中,这次不仅仅是字符串

// add a non-string value for demo
const myObject = {
  ...
  d: 1
};

for (const [ path, value ] of traverse (myObject)) {
  console.log ('path', path)
  console.log ('value', value)
  console.log ('---')
}

// path [ 'a' ]
// value myObject.a
// ---
// path [ 'b', 'ba' ]
// value myObject.b.ba
// ---
// path [ 'b', 'bb', '0' ]
// value myObject.b.bb[0]
// ---
// path [ 'c', '0', 'ca' ]
// value myObject.c[0].ca
// ---
// path [ 'd' ]
// value 1
// ---

如果我们愿意,我们可以使用 Array.from 收集所有对

Array.from (traverse (myObject))
// [ [ [ 'a' ], 'myObject.a' ]
// , [ [ 'b', 'ba' ], 'myObject.b.ba' ]
// , [ [ 'b', 'bb', '0' ], 'myObject.b.bb[0]' ]
// , [ [ 'c', '0', 'ca' ], 'myObject.c[0].ca' ]
// , [ [ 'd' ], 1 ]
// ]

您可能已经注意到,我将path 保留为一个数组,而不是使其成为. 分隔的字符串。没有必要把它变成一个字符串,以后再拆分它。

const lookup = (obj, [ key, ...path ]) =>
  obj && key
    ? lookup (obj [key], path)
    : obj

for (const [ path, value ] of traverse (myObject)) {
  console.log ('path', path)
  console.log ('value', value)
  console.log ('lookup', lookup (myObject, path))
  console.log ('---')
}

// path [ 'a' ]
// value myObject.a
// lookup myObject.a
// ---
// path [ 'b', 'ba' ]
// value myObject.b.ba
// lookup myObject.b.ba
// ---
// path [ 'b', 'bb', '0' ]
// value myObject.b.bb[0]
// lookup myObject.b.bb[0]
// ---
// path [ 'c', '0', 'ca' ]
// value myObject.c[0].ca
// lookup myObject.c[0].ca
// ---
// path [ 'd' ]
// value 1
// lookup 1
// ---

【讨论】:

    猜你喜欢
    • 2017-08-22
    • 2010-10-31
    • 2018-09-12
    • 1970-01-01
    • 2021-07-11
    • 2017-02-20
    • 1970-01-01
    • 2021-08-03
    • 2014-01-28
    相关资源
    最近更新 更多