【问题标题】:Recursive function to add object into deeply nested array of objects adding into wrong object将对象添加到深度嵌套的对象数组中的递归函数添加到错误的对象中
【发布时间】:2022-01-16 14:31:28
【问题描述】:

我有以下对象结构

    layout: [
        {
            type: "FOLDER",
            id: "folder0",
            children: [

                {
                    type: "FILE",
                    id: "file0",
                },
                {
                    type: "FOLDER",
                    id: "folder00",
                    children: [
                        {
                            type: "FILE",
                            id: "file7",
                        }
                    ]
                },
                {
                    type: "FOLDER",
                    id: "folder02",
                    children: [
                        {
                            type: "FILE",
                            id: "file8",
                        }
                    ]
                }
            ]
        },

        {
            type: "FOLDER",
            id: "folder2",
            children: [
                {
                    type: "FILE",
                    id: "file4",
                }
            ]
        },
        {
            type: "FOLDER",
            id: "folder1",
            children: [
                {
                    type: "FILE",
                    id: "file2",
                },
                {
                    type: "FILE",
                    id: "file3",
                }
            ]
        },
        {
            type: "FILE",
            id: "file6",
        }


    ]

我正在使用以下代码添加/删除/移动我从 this example 获取的代码

export const remove = (arr, index) => [
    // part of the array before the specified index
    ...arr.slice(0, index),
    // part of the array after the specified index
    ...arr.slice(index + 1)
];


export const removeChildFromChildren = (children, splitItemPath) => {
    if (splitItemPath.length === 1) {
        const itemIndex = Number(splitItemPath[0]);
        return remove(children, itemIndex);
    }

    const updatedChildren = [...children];

    const curIndex = Number(splitItemPath.slice(0, 1));

    // Update the specific node's children
    const splitItemChildrenPath = splitItemPath.slice(1);
    const nodeChildren = updatedChildren[curIndex];
    updatedChildren[curIndex] = {
        ...nodeChildren,
        children: removeChildFromChildren(
            nodeChildren.children,
            splitItemChildrenPath
        )
    };

    return updatedChildren;
};

export const insert = (arr, index, newItem) => {
    console.log("insideinsert", arr, index, newItem)
    return [
        // part of the array before the specified index
        ...arr.slice(0, index),
        // inserted item
        newItem,
        // part of the array after the specified index
        ...arr.slice(index)
    ];
}


export const addChildToChildren = (children, splitDropZonePath, item, targetid) => {

    if (splitDropZonePath.length === 1) {
        const dropZoneIndex = Number(splitDropZonePath[0]);
        return insert(children, dropZoneIndex, item);
    }



    const updatedChildren = [...children];
    const curIndex = Number(splitDropZonePath.slice(0, 1));
    const splitItemChildrenPath = splitDropZonePath.slice(1);
    const nodeChildren = updatedChildren[curIndex];
    updatedChildren[curIndex] = {
        ...nodeChildren,
        children: addChildToChildren(
            nodeChildren.children,
            splitItemChildrenPath,
            item
        )
    };
    return updatedChildren;
};

export const handleMoveToDifferentParent = (
    layout,
    splitDropZonePath,
    splitItemPath,
    item,
    targetid
) => {

    const removedfromlayout = removeChildFromChildren(layout, splitItemPath);
    return addChildToChildren(
        removedfromlayout,
        splitDropZonePath,
        item,
        targetid
    );
};
handleMoveToDifferentParent(layout, [0,2], [0,0], file0inobjectform)

但是当我用索引路径调用它时。它将 file0 移动到 folder02 而不是 folder00 有没有更简单的方法来做到这一点?我不确定如何按 id 插入,因为索引数组也决定了顺序数组。

【问题讨论】:

    标签: javascript recursion multidimensional-array dynamic-arrays


    【解决方案1】:

    给定不可变助手splice(a, k, trans) where -

    • a 是输入数组
    • k 是要更新的键(索引)
    • trans 是一个函数,它接收k 处的元素并返回一个替换值
    • 返回一个 new 数组,其中 trans(a[k]) 插入位置 k。输入 a 未修改。
    // splice :: ('a array, int, 'a -> 'a) -> 'a array
    function splice(a, k, trans) {
      return a.slice(0, k).concat(trans(a[k])).concat(a.slice(k + 1))
    }
    

    和不可变的助手extract(t, path) where -

    • t 是树中的一个节点,形状为 { id, type, children? }
    • path 是一个索引数组
    • 返回[selected, tprime],其中selectedpath 末尾的节点,tprime 是一个 树,其中删除了selected。输入树 t 未发生突变。

    此实现特别注意仅遵循有效的索引路径。 extract 将在尝试获取 FILE 的后代或 node.type 既不是 FOLDER 也不是 FILE 时抛出错误 -

    // extract :: ('a tree, int array) -> ('a, 'a tree)
    function extract(t, path) {
      function loop(t, [i, ...path], cont) {
        if (i == null)
          return cont(t, _ => [])
        else switch (t?.type) {
          case "FOLDER":
            return loop(t.children[i], path, (selected, trans) =>
              cont(selected, a => ({ ...a, children: splice(a.children, i, trans) }))
            )
          case "FILE":
            throw Error(`cannot get descendant of file: ${JSON.stringify(t)}`)
          default:
            throw Error(`unsupported type: ${JSON.stringify(t)}`)
        }
      }
      return path.length == 0
        ? [undefined, t]
        : loop(t, path, (selected, trans) => [selected, trans(t)])
    }
    

    给定一个输入树 mytree,注意添加了一个 root 节点,因此最多有 一个根节点 -

    const mytree = {
      id: "root",
      type: "FOLDER",
      children: [
        {
          id: "foo",
          type: "FOLDER",
          children: [
            { id: "foo1", type: "FILE" },
            { id: "foo2", type: "FILE" }
          ]
        },
        {
          id: "bar",
          type: "FOLDER",
          children: [
            { id: "bar1", type: "FILE" }
          ]
        }
      ]
    }
    

    让我们按照索引[0,1]提取foo2

    const [selected, tprime] = extract(mytree, [0,1])
    console.log("selected", selected)
    console.log("new tree", tprime)
    
    selected {
      id: "foo2",
      type: "FILE"
    }
    
    new tree {
      id: "root",
      type: "FOLDER",
      children: [
        {
          id: "foo",
          type: "FOLDER",
          children: [
            { id: "foo1", type: "FILE" }
          ]
        },
        {
          id: "bar",
          type: "FOLDER",
          children: [
            { id: "bar1", type: "FILE" }
          ]
        }
      ]
    }
    

    我们可以通过选择[1] 提取所有bar -

    const [selected, tprime] = extract(mytree, [1])
    console.log("selected", selected)
    console.log("new tree", tprime)
    
    selected {
      id: "bar",
      type: "FOLDER",
      children: [
        { id: "bar1", type: "FILE" }
      ]
    }
    
    new tree {
      id: "root",
      type: "FOLDER",
      children: [
        {
          id: "foo",
          type: "FOLDER",
          children: [
            { id: "foo1", type: "FILE" },
            { id: "foo2", type: "FILE" }
          ]
        }
      ]
    }
    

    仅用 26 行代码,我们就已经解决了一半以上的问题。节点插入要容易得多,留给读者作为练习 -

    function insert(t, path, node) {
      //...
    }
    
    function move(t, srcPath, destPath) {
      const [selectedNode, newTree] = extract(t, srcPath)
      return insert(newTree, destPath, selectedNode)
    }
    

    如需完整演示本文中的代码,请展开下面的 sn-p 并在您自己的浏览器中验证结果 -

    function splice(a, k, trans) {
      return a.slice(0, k).concat(trans(a[k])).concat(a.slice(k + 1))
    }
    
    function extract(t, path) {
      function loop(t, [i, ...path], cont) {
        if (i == null)
          return cont(t, _ => [])
        else switch (t?.type) {
          case "FOLDER":
            return loop(t.children[i], path, (selected, trans) =>
              cont(selected, a => ({ ...a, children: splice(a.children, i, trans) }))
            )
          case "FILE":
            throw Error(`cannot get descendant of file: ${JSON.stringify(t)}`)
          default:
            throw Error(`unsupported type: ${JSON.stringify(t)}`)
        }
      }
      return path.length == 0
        ? [undefined, t]
        : loop(t, path, (selected, trans) => [selected, trans(t)])
    }
    
    const t = 
      { id: "root", type: "FOLDER", children: [
        { id: "foo", type: "FOLDER", children: [
          { id: "foo1", type: "FILE" },
          { id: "foo2", type: "FILE" }
        ]},
        { id: "bar", type: "FOLDER", children: [
          { id: "bar1", type: "FILE" }
        ]}
      ]}
      
    const [selected, tprime] = extract(t, [0,0])
    console.log("selected", selected)
    console.log("new tree", tprime)

    【讨论】:

    • 尽量避免cargo cult programming。在将代码复制/粘贴到程序中之前,您应该花时间阅读示例并理解代码。如果您有任何问题,我很乐意为您提供帮助。
    猜你喜欢
    • 1970-01-01
    • 2018-02-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 2023-01-12
    • 1970-01-01
    • 2022-01-13
    相关资源
    最近更新 更多