根据第n个深层嵌套对象中的属性过滤对象

时间:2020-04-19 23:52:10

标签: javascript dictionary recursion filter tree

我有一棵树,它将包含的嵌套对象数量有所不同。通用示例如下-

tagClass: Variable

我试图删除所有具有const treeData = { id: 1, title: "Group1", tagClass: "Object", tagIds: [6] children: [ { id: 2, title: "Group2", tagClass: "Object", tagIds: [3,4] }, { id: 5, title: "Group3", tagClass: "Object" }, ], }; 作为属性的嵌套对象,然后将该对象ID的引用作为其父对象的属性留在数组中-

const recursiveFunc = (treeData) => {
    if (treeData.children) {
        treeData.children = treeData.children
            .filter((child) => child.tagClass === "Object")
            .map((child) => recursiveFunc(child));
        return treeData;
    }
};

const updatedTreeData = recursiveFunc(treeData);

我知道.filter,.map和递归将是此的便捷工具,但是我很快就会遇到困难。非常感谢解决该算法问题的任何帮助。我尝试过的部分内容-

fetch API

感谢能帮助您解决此问题的聪明人。干杯。

4 个答案:

答案 0 :(得分:2)

recursiveFunc方法基于您的想法。在过滤期间创建tagIds属性。有关详细信息,请参见代码段中的注释。

const treeData = {
    id: 1,
    title: "Group1",
    tagClass: "Object",
    children: [
        {
            id: 2,
            title: "Group2",
            tagClass: "Object",
            children: [
                { id: 3, title: "Tag1", tagClass: "Variable" },
                { id: 4, title: "Tag2", tagClass: "Variable" },
            ],
        },
        { id: 5, title: "Group3", tagClass: "Object" },
        { id: 6, title: "Tag3", tagClass: "Variable" },
    ],
};

const recursiveFunc = (treeData) => {
  if(treeData.children){
     //filter treeData.children
     const children = treeData.children.filter(child => {
      if(child.tagClass === 'Variable'){
        //if tagclass is variable we create tagIds property for treeData
        treeData.tagIds? treeData.tagIds.push(child.id) : treeData.tagIds = [child.id];
        // return false to filter out this child
        return false
      }
      //not varaible tagclass, we go deeper
      recursiveFunc(child);
      //keep the child
      return true
    })
    //if children is an empty array, delete children property from treeData
    children.length === 0 ? delete treeData.children : treeData.children = children
  }
  return treeData
};

const updatedTreeData = recursiveFunc(treeData);

console.log(updatedTreeData)

答案 1 :(得分:2)

递归数据用于递归程序,用于递归数据

您可以使用inductive reasoning编写递归transform函数。 transform接受一个输入o和一个函数test,该函数接收一个对象并在(且仅当)应该转换该对象的情况下返回true

  1. 如果输入o是一个数组,则transform每个子v都具有相同的test
  2. 归纳推理说,输入o不是数组。如果输入是对象,并且它通过了test,则修剪该对象并仅返回对输入的id的引用
  3. 归纳推理说输入o是一个对象,传递test。映射到输入对象,并transform的每个子值v具有相同的test
  4. 归纳推理说,输入o not 的数组,而 not 是一个对象。输入是一个简单值,例如字符串"foo"或数字1。返回未经{transform-ed的输入。
const transform = (o = {}, test = identity) =>
  Array.isArray(o)
    ? o.map(v => transform(v, test))          // 1
  : Object(o) === o
    ? test(o)
      ? o.id                                  // 2
      : objectMap(o, v => transform(v, test)) // 3
  : o                                         // 4

将工作分流到objectMap函数使我们更容易解决问题,并通过使用通用过程促进代码重用-

const identity = x =>
  x

const objectMap = (o = {}, f = identity) =>
  Object.fromEntries(
    Object.entries(o).map(([ k, v ]) => [ k, f(v) ])
  )
  
const example =
  objectMap
    ( { a: 1, b: 2, c: 3, d: 4 }
    , x => x * x                // <-- square each value
    )

console.log(example)
// { a: 1, b: 4, c: 9, d: 16 }  // <-- squared

我们像高阶函数一样使用transform,例如.filter-

const result =
  transform
    ( treeData   // <-- input
    , x => x.tagClass === "Variable" // <-- test
    )

console.log(result)

输出-

{ id: 1
, title: "Group1"
, tagClass: "Object"
, children:
  [ { id: 2
    , title: "Group2"
    , tagClass: "Object"
    , children: [ 3, 4 ] // <-- transformed 3 and 4
    }
  , { id: 5
    , title: "Group3"
    , tagClass: "Object"
    }
  , 6  // <-- transformed 6
  ]
}

代码沙箱

展开以下代码段,以在您自己的浏览器中验证结果-

const identity = x =>
  x

const objectMap = (o = {}, f = identity) =>
  Object.fromEntries(
    Object.entries(o).map(([ k, v ]) => [ k, f(v) ])
  )

const transform = (o = {}, test = identity) =>
  Array.isArray(o)
    ? o.map(v => transform(v, test))
  : Object(o) === o
    ? test(o)
      ? o.id
      : objectMap(o, v => transform(v, test))
  : o

const treeData =
  {id:1,title:"Group1",tagClass:"Object",children:[{id:2,title:"Group2",tagClass:"Object",children:[{id:3,title:"Tag1",tagClass:"Variable"},{id:4,title:"Tag2",tagClass:"Variable"}]},{id:5,title:"Group3",tagClass:"Object"},{id:6,title:"Tag3",tagClass:"Variable"}]}

const result =
  transform
    ( treeData
    , ({ tagClass = "" }) => tagClass === "Variable"
    )

console.log(JSON.stringify(result, null, 2))


提高可读性

递归是一种功能继承,因此将递归与功能样式一起使用可产生最佳效果。函数式编程旨在降低复杂性并重用定义良好的泛型函数。我认为以下抽象使transform更好-

const isArray =
  Array.isArray

const isObject = o =>
  Object(o) === o

const transform = (o = {}, test = identity) =>
  isArray(o)
    ? o.map(v => transform(v, test))        // 1
  : isObject(o) && test(o)
    ? o.id                                  // 2
  : isObject(o)
    ? objectMap(o, v => transform(v, test)) // 3
  : o                                       // 4

const result =
  transform
    ( treeData
    , ({ tagClass = "" }) =>
        tagClass === "Variable"
    )

console.log(result)

程序没有做什么

  1. 更改输入内容或产生其他副作用
  2. childrentagIds进行假设
  3. 不必要地检查数组length

应该使o.id有点不适。如果我们想在不同情况下以不同的方式塑造结果怎么办?为什么id转换要一成不变?

通过定义另一个功能参数prune ...

const transform = (o = {}, test = identity, prune = identity) =>
  isArray(o)
    ? o.map(v => transform(v, test, prune))    // <-- pass prune
  : isObject(o) && test(o)
    ? prune(o)                                 // <-- prune!
  : isObject(o)
    ? objectMap(o, v => transform(v, test, prune)) // <-- pass prune
  : o

现在,我们可以定义如何transform在呼叫站点上运行test并执行prune-

const result =
  transform
    ( treeData
    , ({ tagClass = "" }) =>     
        tagClass === "Variable"   // <-- test
    , ({ id = 0, title = "" }) => 
        ({ id, title })           // <-- return only { id, title }
    )

输出-

{ id: 1
, title: "Group1"
, tagClass: "Object"
, children:
  [ { id: 2
    , title: "Group2"
    , tagClass: "Object"
    , children:
        [ { id: 3, title: "Tag1" } // <--  prune { id, title }
        , { id: 4, title: "Tag2" } // <-- prune { id, title }
        ]
    }
  , { id: 5
    , title: "Group3"
    , tagClass: "Object"
    }
  , { id: 6, title: "Tag3" } // <-- prune { id, title }
  ]
}

答案 2 :(得分:2)

这是我的方法:

const transform = ({children, ...rest}) => {
  const kids = (children || []) .filter (({tagClass}) => tagClass !== 'Variable')
  const tags = (children || []) .filter (({tagClass}) => tagClass === 'Variable')

  return {
    ... rest,
    ... (tags .length ? {tagIds: tags .map (({id}) => id)} : {}),
    ... (kids .length ? {children: kids .map (transform)} : {})
  }
}

const treeData = {id: 1, title: "Group1", tagClass: "Object", children: [{id: 2, title: "Group2", tagClass: "Object", children: [{id: 3, title: "Tag1", tagClass: "Variable"}, {id: 4, title: "Tag2", tagClass: "Variable"}]}, {id: 5, title: "Group3", tagClass: "Object"}, {id: 6, title: "Tag3", tagClass: "Variable"}]}

console .log (
  transform (treeData)
)

我们将Variables与其他变量分开,将变量的id属性收集到tagIds中,然后在其余子项上重复使用。可以通过允许的partition函数

对此进行改进
const [tags, kids] = 
  partition (({tagClass}) => tagClass === 'Variable')) (children) 

但是我留给你。

答案 3 :(得分:1)

这是使用递归的就地版本。您可以将父级传递到递归调用中,也可以返回父级,以决定是否应将子级保留在children数组中,并相应地重新排列父级。

const moveVarIdToParent = root => {
  if (root.children) {
    const children = root.children.map(e => [e, moveVarIdToParent(e)]);
    root.tagIds = children.filter(e => e[1]).map(e => e[0].id);
    root.children = children.filter(e => !e[1]).map(e => e[0]);

    if (!root.children.length) {
      delete root.children;
    }

    if (!root.tagIds.length) {
      delete root.tagIds;
    }
  }

  return root.tagClass === "Variable";
};

const treeData = {
    id: 1,
    title: "Group1",
    tagClass: "Object",
    children: [
        {
            id: 2,
            title: "Group2",
            tagClass: "Object",
            children: [
                { id: 3, title: "Tag1", tagClass: "Variable" },
                { id: 4, title: "Tag2", tagClass: "Variable" },
            ],
        },
        { id: 5, title: "Group3", tagClass: "Object" },
        { id: 6, title: "Tag3", tagClass: "Variable" },
    ],
};

moveVarIdToParent(treeData);
console.log(treeData);