Фильтровать объект на основе атрибута внутри n-го глубоко вложенного объекта - PullRequest
1 голос
/ 20 апреля 2020

У меня есть дерево, которое будет меняться в зависимости от количества вложенных объектов. Обобщенный пример выглядит так -

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" },
    ],
};

Я пытаюсь удалить все вложенные объекты, которые имеют 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" },
    ],
};

Я знаю, что .filter, .map и recursion будут удобными инструментами для этого, но я подхожу к концу и близок к концу. Любая помощь для решения этой проблемы алгоритма c очень ценится. Часть того, что я пробовал -

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);

Спасибо интеллектуальному уму, который может помочь решить эту проблему. Приветствия.

Ответы [ 4 ]

2 голосов
/ 20 апреля 2020

Это мой подход:

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) 

, но я оставлю это вам.

2 голосов
/ 20 апреля 2020

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)
2 голосов
/ 20 апреля 2020

рекурсивные данные предназначены для рекурсивных программ и предназначены для рекурсивных данных

Вы можете написать рекурсивную функцию transform, используя индуктивное рассуждение . transform принимает на вход o и функцию test, которая получает объект и возвращает true, если (и только если) объект должен быть преобразован.

  1. Если на входе , o, это массив, transform каждый дочерний элемент, v, с тем же test
  2. Индуктивное рассуждение говорит, что ввод, o, не an массив. Если вход является объектом, и он проходит test, обрежьте этот объект и верните только ссылку на входные данные id
  3. Индуктивное рассуждение говорит, что вход, o, является объектом и делает не передать test. Сопоставьте входной объект и transform каждое дочернее значение, v, с тем же test
  4. . Индуктивное рассуждение говорит, что ввод, o, является , а не массивом и не объект. Ввод представляет собой простое значение, такое как строка "foo" или число 1. Возвратите ввод un- 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 облегчает нам решение нашей проблемы и способствует повторному использованию кода посредством использования generi c процедуры -

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))

улучшение читабельности

Рекурсия - это функциональное наследие, поэтому использование рекурсии с функциональным стилем дает наилучшие результаты. Функциональное программирование сводится к уменьшению сложности и повторному использованию четко определенных обобщенных функций c. Я думаю, что следующие абстракции делают 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. делать предположения о children или tagIds
  3. без необходимости проверять length массивов

Какие должно заставить это o.id чувствовать себя немного неуместным. Что если мы хотим по-разному формировать результаты в разных сценариях ios? Почему преобразование 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 }
  ]
}
1 голос
/ 20 апреля 2020

Вот версия на месте с использованием рекурсии. Вы можете передать родительский элемент в рекурсивный вызов или вернуть его родителю, должен ли дочерний элемент быть сохранен в массиве 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...