функциональный стиль
Вы были на правильном пути со своим ответом, однако повторение в зависимости от длины предоставленной пользователем процедуры является ошибкой.Вместо этого путь переменной длины должен быть передан как одно значение переменной длины - массив
const reduceTree = (proc, state, tree, path = []) =>
reduce // call reduce with:
( (acc, [ key, value ]) => // reducer
isObject (value) // value is an object (another tree):
? reduceTree // recur with:
( proc // the proc
, acc // the acc
, value // this value (the tree)
, append (path, key) // add this key to the path
) // value is NOT an object (non-tree):
: proc // call the proc with:
( acc // the acc
, value // this value (non-tree, plain value)
, append (path, key) // add this key to the path
)
, state // initial input state
, Object.entries (tree) // [ key, value ] pairs of input tree
)
Свободные значения выше определены для использования префиксной нотации, которая более знакома в функциональном стиле -
const isObject = x =>
Object (x) === x
const reduce = (proc, state, arr) =>
arr .reduce (proc, state)
const append = (xs, x) =>
xs .concat ([ x ])
Теперь у нас есть общая reduceTree
функция -
const result =
reduceTree
( (acc, value, path) => // reducer
[ ...acc, { path, value } ]
, [] // initial state
, input // input tree
)
console.log (result)
// [ { path: [ 'a1', 'b1', 'c1', 'd1', 'e1' ], value: 'one' }
// , { path: [ 'a1', 'b1', 'c1', 'd1', 'e2' ], value: 'two' }
// , { path: [ 'a1', 'b1', 'c1', 'd1', 'e3' ], value: 'three' }
// , { path: [ 'a1', 'b1', 'c1', 'd2', 'e1' ], value: 'one' }
// , { path: [ 'a1', 'b1', 'c1', 'd2', 'e2' ], value: 'two' }
// , { path: [ 'a1', 'b1', 'c1', 'd2', 'e3' ], value: 'three' }
// , { path: [ 'a1', 'b1', 'c2', 'd1', 'e1' ], value: 'one' }
// , { path: [ 'a1', 'b1', 'c2', 'd1', 'e2' ], value: 'two' }
// , { path: [ 'a2', 'b1', 'c1', 'd1', 'e1' ], value: 'one' }
// , { path: [ 'a2', 'b1', 'c1', 'd2', 'e1' ], value: 'one' }
// , { path: [ 'a2', 'b2', 'c1', 'd1', 'e1' ], value: 'one' }
// , { path: [ 'a2', 'b2', 'c1', 'd2', 'e1' ], value: 'one' }
// ]
Мы можем формировать результат результата так, как нам нравится -
const result =
reduceTree
( (acc, value, path) => // reducer
({ ...acc, [ path .join ('.') ]: value })
, {} // initial state
, input // input tree
)
console.log (result)
// { 'a1.b1.c1.d1.e1': 'one'
// , 'a1.b1.c1.d1.e2': 'two'
// , 'a1.b1.c1.d1.e3': 'three'
// , 'a1.b1.c1.d2.e1': 'one'
// , 'a1.b1.c1.d2.e2': 'two'
// , 'a1.b1.c1.d2.e3': 'three'
// , 'a1.b1.c2.d1.e1': 'one'
// , 'a1.b1.c2.d1.e2': 'two'
// , 'a2.b1.c1.d1.e1': 'one'
// , 'a2.b1.c1.d2.e1': 'one'
// , 'a2.b2.c1.d1.e1': 'one'
// , 'a2.b2.c1.d2.e1': 'one'
// }
input
для нашего теста необходимо продемонстрировать, что reduceTree
работает для различных уровней вложенности -
test ('better', () => {
const input =
{ a: { b: { c: 1, d: 2 } }, e: 3 }
const expected =
{ 'a.b.c': 1, 'a.b.d': 2, e: 3 }
const result =
reduceTree
( (acc, value, path) =>
({ ...acc, [ path .join ('.') ]: value })
, {}
, input
)
expect(result).toEqual(expected)
})
Наконец, убедитесь, что программа работает в вашем браузере ниже -
const isObject = x =>
Object (x) === x
const reduce = (proc, state, arr) =>
arr .reduce (proc, state)
const append = (xs, x) =>
xs .concat ([ x ])
const reduceTree = (proc, state, tree, path = []) =>
reduce
( (acc, [ key, value ]) =>
isObject (value)
? reduceTree
( proc
, acc
, value
, append (path, key)
)
: proc
( acc
, value
, append (path, key)
)
, state
, Object.entries (tree)
)
const input =
{ a: { b: { c: 1, d: 2 } }, e: 3 }
const result =
reduceTree
( (acc, value, path) =>
[ ...acc, { path, value } ]
, []
, input
)
console.log (result)
// { 'a.b.c': 1, 'a.b.d': 2, e: 3 }
… с помощью друзей
Генераторы императивного стиля облегчают работу такого рода задач, в то время какпредлагая интуитивно понятный язык для описания предполагаемого процесса.Ниже мы добавляем traverse
, который генерирует [ path, value ]
пар для вложенного tree
(объект) -
const traverse = function* (tree = {}, path = [])
{ for (const [ key, value ] of Object.entries (tree))
if (isObject (value))
yield* traverse (value, append (path, key))
else
yield [ append (path, key), value ]
}
Используя Array.from
, мы можем подключить генератор напрямую к нашему существующему функционалу reduce
;reduceTree
теперь просто специализация -
const reduceTree = (proc, state, tree) =>
reduce
( (acc, [ path, value ]) =>
proc (acc, value, path)
, state
, Array.from (traverse (tree))
)
Сайт вызова тот же -
const input =
{ a: { b: { c: 1, d: 2 } }, e: 3 }
const result =
reduceTree
( (acc, value, path) =>
({ ...acc, [ path .join ('.') ]: value })
, {}
, input
)
console.log (result)
// { 'a.b.c': 1, 'a.b.d': 2, e: 3 }
Проверьте результат в вашем браузере ниже -
const isObject = x =>
Object (x) === x
const reduce = (proc, state, arr) =>
arr .reduce (proc, state)
const append = (xs, x) =>
xs .concat ([ x ])
const traverse = function* (tree = {}, path = [])
{ for (const [ key, value ] of Object.entries (tree))
if (isObject (value))
yield* traverse (value, append (path, key))
else
yield [ append (path, key), value ]
}
const reduceTree = (proc, state, tree) =>
reduce
( (acc, [ path, value ]) =>
proc (acc, value, path)
, state
, Array.from (traverse (tree))
)
const input =
{ a: { b: { c: 1, d: 2 } }, e: 3 }
const result =
reduceTree
( (acc, value, path) =>
({ ...acc, [ path .join ('.') ]: value })
, {}
, input
)
console.log (result)
// { 'a.b.c': 1, 'a.b.d': 2, e: 3 }