рекурсивные данные предназначены для рекурсивных программ и предназначены для рекурсивных данных
Вы можете написать рекурсивную функцию transform
, используя индуктивное рассуждение . transform
принимает на вход o
и функцию test
, которая получает объект и возвращает true
, если (и только если) объект должен быть преобразован.
- Если на входе ,
o
, это массив, transform
каждый дочерний элемент, v
, с тем же test
- Индуктивное рассуждение говорит, что ввод,
o
, не an массив. Если вход является объектом, и он проходит test
, обрежьте этот объект и верните только ссылку на входные данные id
- Индуктивное рассуждение говорит, что вход,
o
, является объектом и делает не передать test
. Сопоставьте входной объект и transform
каждое дочернее значение, v
, с тем же test
- . Индуктивное рассуждение говорит, что ввод,
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)
что программа не делает
- видоизменять вход или иметь другие побочные эффекты
- делать предположения о
children
или tagIds
- без необходимости проверять
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 }
]
}