【问题标题】:Working With Array Of Objects使用对象数组
【发布时间】:2016-04-29 16:09:08
【问题描述】:

我有一个对象数组,可以包含相同对象类型的子对象,如下所示:

var exampleArray = [
    {
        alias: 'alias1',
        children: [
            {
                alias: 'child1'
            },
            {
                alias: 'child2',
                children: [
                    {
                        alias: 'child4'
                    },
                    {
                        alias: 'child5'
                    }
                ]
            },
            {
                alias: 'child3'
            }
        ]
    },
    {
        alias: 'alias2'
    },
    {
        alias: 'alias3',
        children: [
            {
                alias: 'child6'
            },
            {
                alias: 'child7'
            }
        ]
    }
];

基础对象具有其他属性,但它们对于手头的问题并不重要。现在,让我们假设对象可以是:

{
    alias: 'string',
    children: []
}

孩子是可选的。

我正在寻找使用这样的对象管理某些事物的最佳方法/最快方法。我创建了一些递归方法来做一些我想做的事情,但我想知道是否有更好的方法来完成以下任务:

  1. hasAlias(arr, alias) - 我需要确定整个对象是否包含任何具有给定别名的对象。

目前,我以递归方式执行此操作,但鉴于此数组可以无限增长,递归方法最终会达到堆栈限制。

  1. getParent(arr, alias) - 我需要能够获取包含具有给定别名的元素的父级。鉴于别名'对于整个数组是唯一的,永远不会有两个相同的别名。我现在再次递归地执行此操作,但我想找到更好的方法来执行此操作。

  2. deleteObject(arr, alias) - 我不确定目前如何完成这个。我需要能够传递一个数组和一个别名,并从给定数组中删除该对象(及其所有子对象)。我开始使用递归方法来做这件事,但后来停下来决定在这里发帖。

我正在使用 Node.js,并且有 lodash 可用于更快的处理方法。我对 JavaScript 还是很陌生,所以我不确定是否有更好的方法来处理像这样的大规模数组。

【问题讨论】:

  • 我认为您可以将整个数组分成两个或三个,然后分别调用您当前的方法。这样你就可以通过一个特定的。你会有很好的进步。
  • 不确定我会这样做,但作为旁注hasAlias 可以使用类似JSON.stringify(arr).indexOf('alias') 的东西来完成,如果alias 这个词没有出现在其他任何地方等。
  • 我认为递归是有道理的,除非你有数千个嵌套的孩子,否则你不太可能达到堆栈限制。您总是可以尝试使其尾递归并蹦床,或运行转译器将其转换为循环。
  • 你会考虑用不道德的方式来加快速度吗?当前版本的 Node.js 列出了对 Map and WeakMap 的支持,这在这方面可能很有用,但会引入如何构建和维护索引的复杂性。
  • 我无法更改对象的状态,因为它们是我无法编辑的预定义数据。

标签: javascript arrays node.js recursion lodash


【解决方案1】:

在不支持递归的 FORTRAN 时代,通过更改数据集以模拟“递归”级别来实现类似的效果。将此原理应用于示例对象结构,可以编写一个通过“别名”(另一个词的名称或 id)查找对象的函数,无需递归,如下所示:

function findAlias( parent, alias) // parent object, alias value string
{   function frame( parent)
    {   return {parent: parent, children: parent.children,
        index: 0, length: parent.children.length};
    }
    var stack, tos, child, children, i;

    stack = [];
    if( parent.children)
        stack.push( frame( parent));

    search:
    while( stack.length)
    {   tos = stack.pop();  // top of generation stack
        children = tos.children;
        for( i = tos.index; i < tos.length; ++i)
        {   child = children[i]; 
            if( child.alias == alias)
            {   return { parent: tos.parent, child: child, childIndex: i}
            }
            if( child.children)
            {   tos.index = i + 1;
                stack.push(tos);  // put it back
                stack.push( frame(child));
                continue search;
            }
        }
    }
    return null;
}

简而言之,最终会创建一堆小的数据对象,这些对象在同一个函数中被推送和弹出,而不是进行递归调用。上面的示例返回带有 parentchild 对象值的对象。子值是具有提供的别名属性的值,父对象是其children 数组中包含子对象的值。

如果找不到别名,则返回 null,因此可用于 hasAlias 功能。如果它不返回 null,它将执行 getParent 功能。但是,您必须创建一个根节点:

// create a rootnode
var rootNode = { alias: "root", children: exampleArray}; 
var found = findAlias(rootNode, "alias3");
if( found)
{  console.log("%s is a child of %s, childIndex = %s",
       found.child.alias, found.parent.alias, found.childIndex);
}
else
   console.log("not found");


[编辑:添加 childIndex 到搜索返回对象,更新测试示例代码,添加结论。]

结论

在支持树遍历应用程序时使用递归函数调用在自记录代码和可维护性方面是有意义的。如果可以证明在容量压力测试下它在减少服务器负载方面具有显着优势,但需要完善的文档,则非递归变体可能会为自己带来回报。

不管内部编码如何,返回包含父、子和子索引值详细信息的对象的树遍历函数可能会通过减少曾经执行的树遍历总数来提高整体程序效率:

  • 搜索返回值的真实性替代了hasAlias 函数
  • 搜索的返回对象可以传递给更新、删除或插入函数,而无需在每个函数中重复进行树搜索。

【讨论】:

  • 它不断地在每次搜索时分配内存。在服务器上可能是致命的。在客户端上只会变得迟缓
【解决方案2】:

保证最快的方法显然是有

- an index for the aliases (thats actually a unique id)
- have a parent backlink on each child item (if it has a parent)

你查看id索引

var index = {}
(function build(parent) {
   index[parent.alias] = parent;
   (parent.children || []).forEach( item => {
       item.parent = parent
       build(item)
   })
})(objectRoot)


function hasAlias(alias) { return alias in index }

function getAlias(alias) { return index[alias] } 

function getParent(alias) { return index[alias] && index[alias].parent}

删除别名意味着将其及其子项从索引以及仍保留在索引中的父项中删除

function deleteAlias(alias) {

   function deleteFromIndex(item) {
      delete index[item.alias]
      (item.children || []).forEach(deleteFromIndex)
   }
   var item = index[alias]
   item.parent.children.splice(item.parent.children.indexOf(item))
   deleteFromIndex(item)

}

【讨论】:

    【解决方案3】:

    我可能会稍微不同地处理您的主数组,并将其保留为引用其他项目而不是完全合并它们的平面数组。

    var flat = [
        {
            alias : "str1",
            children : [ flat[1], flat[2] ],
            parent : null   
        },
    
        {
            alias : "str1",
            children : [],
            parent : flat[0]    
        },
    
        {
            alias : "str1",
            children : [],
            parent : flat[0]    
        }
    ]
    

    这是一种“linked list”的方法。链表有利有弊,但您可以快速迭代所有项目。

    【讨论】:

      猜你喜欢
      • 2021-04-29
      • 2017-12-22
      • 1970-01-01
      • 1970-01-01
      • 2016-08-06
      • 1970-01-01
      • 2013-04-22
      • 2023-03-04
      相关资源
      最近更新 更多