Find path in tree by direct path

北城以北 提交于 2019-12-23 23:27:12

问题


I have similiary problem like here: JavaScript: Find all parents for element in tree recursive

But I don't find path by name but by direct path.

const path = ["name1", "name4", "name5"];

const data = [
    {
        'name': 'name1',
        'tree': [
            {'name': 'name2'},
            {'name': 'name3'},
            {
                'name': 'name4',
                'tree': [
                    {'name': 'name5'},
                    {'name': 'name6'}
                ]
            },
            {'name': 'name7'}
        ]
    },
    {
        'name': 'name8',
        'tree': [
            {'name': 'name9'}
        ]
    }
];

It returns every possible path or nothing.

When path is too short, it returns nothing.

When path is too long, it returns nothing.

Thanks for help!

Examples of desired output:

const path = ["name1", "name4", "name5"];
findAPath(data, path) 

Returns: ["name1", "name4", "name5"]

const path = ["name1", "name7", "name5"];
findAPath(data, path)

Returns []

const path = ["name1", "name4", "name5", "name5"];
findAPath(data, path) 

Returns []

My trying:

let index = 0;
function find(data, index) {
    let index = index;
    data.some((o) => {
        if(o.name == path[index]) {
            index++;
            find(o.tree, index);
        }
    });
    // I don't know what return here.
    // I need to probably return path where I am.
    return <>;
}

回答1:


using Array.prototype.flatMap

Here's a functional solution using a mutual recursion technique -

const None =
  Symbol ()

const findPath = (tree = [], names = [], r = []) =>
  tree.length && names.length                              // base: and
    ? tree.flatMap(branch => findPath1(branch, names, r))
    : tree.length || names.length                          // inductive: xor
        ? []
        : [ r ]                                            // inductive: nor                                     // inductive: nor

const findPath1 = ({ name = "", tree = [] } = {}, [ q = None, ...more ] = [], r = []) =>
  name === "" && q === None                    // base: and
    ? [ r ]
    : name === "" || q === None || name !== q  // inductive: xor
        ? []
        : findPath(tree, more, [ ...r, q ])    // inductive: nor

findPath(data, ["name1", "name4", "name5"])
// => [ [ "name1", "name4", "name5" ] ]

NB if your data contains multiple paths to the input values, all paths will be returned -

const data = [
  {
      'name': 'name1',                   // name1
      'tree': [
          {'name': 'name2'},
          {'name': 'name3'},
          {
              'name': 'name4',           // name1->name4
              'tree': [
                  {'name': 'name5'},     // name1->name4->name5
                  {'name': 'name6'}
              ]
          },
          {
            'name': 'name4',             // name1->name4
            'tree': [
                {'name': 'name5'},       // name1->name4->name5
                {'name': 'name6'}
              ]
          },
          {'name': 'name7'}
      ]
  },
  {
      'name': 'name8',
      'tree': [
          {'name': 'name9'}
      ]
  }
]

Just like you asked, it returns every possible path, or nothing -

findPath(data, ["name1", "name4", "name5"])
// => [ [ "name1", "name4", "name5" ],
//      [ "name1", "name4", "name5" ] ]

findPath(data, [ "name1", "name7" ])
// => [ [ "name1", "name7" ] ]

findPath(data, [ "name1", "name9" ])
// => []

When a path is too short or too long, it will return nothing -

findPath(data, [ "name1", "name4" ])
// => []

findPath(data, [ "name1", "name4", "name5", "name6" ])
// => []

Expand the snippet below to verify the results in your own browser -

const None =
  Symbol ()

const findPath = (tree = [], names = [], r = []) =>
  tree.length && names.length
    ? tree.flatMap(branch => findPath1(branch, names, r))
    : tree.length || names.length
        ? []
        : [ r ]

const findPath1 = ({ name = "", tree = [] } = {}, [ q = None, ...more ] = [], r = []) =>
  name === "" && q === None
    ? [ r ]
    : name === "" || q === None || name !== q
        ? []
        : findPath(tree, more, [ ...r, q ])

const data = [
  {
      'name': 'name1',
      'tree': [
          {'name': 'name2'},
          {'name': 'name3'},
          {
              'name': 'name4',
              'tree': [
                  {'name': 'name5'},
                  {'name': 'name6'}
              ]
          },
          {'name': 'name7'}
      ]
  },
  {
      'name': 'name8',
      'tree': [
          {'name': 'name9'}
      ]
  }
]

console.log(findPath(data, ["name1", "name4", "name5"]))
// [ [ "name1", "name4", "name5" ] ]

console.log(findPath(data, [ "name1", "name7" ]))
// [ [ "name1", "name7" ] ]

console.log(findPath(data, [ "name1", "name9" ]))
// []

using Generators

Here's an alternative implementation using generators -

const None =
  Symbol ()

const findPath = function* (tree = [], names = [], r = [])
{ if (tree.length && names.length)        // base: and
    for (const branch of tree)
      yield* findPath1(branch, names, r)
  else if (tree.length || names.length)   // inductive: xor
    return
  else                                    // inductive: nor
    yield r
}

const findPath1 = function* ({ name = "", tree = [] } = {}, [ q = None, ...more ] = [], r = [])
{ if (name === "" && q === None)                     // base: and
    yield r
  else if (name === "" || q === None || name !== q)  // inductive: xor
    return
  else                                               // inductive: nor
    yield* findPath(tree, more, [ ...r, q ])
}

It has the exact same output as above, only to coerce the iterable generator into an array, we use Array.from -

Array.from(findPath(data, ["name1", "name4", "name5"]))
// => [ [ "name1", "name4", "name5" ] ]

Array.from(findPath(data, [ "name1", "name7" ]))
// => [ [ "name1", "name7" ] ]

Array.from(findPath(data, [ "name1", "name9" ]))
// => []

Expand the snippet below to verify the results in your own browser -

const None =
  Symbol ()

const findPath = function* (tree = [], names = [], r = [])
{ if (tree.length && names.length)
    for (const branch of tree)
      yield* findPath1(branch, names, r)
  else if (tree.length || names.length)
    return
  else
    yield r
}

const findPath1 = function* ({ name = "", tree = [] } = {}, [ q = None, ...more ] = [], r = [])
{ if (name === "" && q === None)
    yield r
  else if (name === "" || q === None || name !== q)
    return
  else
    yield* findPath(tree, more, [ ...r, q ])
}

const data = [
  {
      'name': 'name1',
      'tree': [
          {'name': 'name2'},
          {'name': 'name3'},
          {
              'name': 'name4',
              'tree': [
                  {'name': 'name5'},
                  {'name': 'name6'}
              ]
          },
          {'name': 'name7'}
      ]
  },
  {
      'name': 'name8',
      'tree': [
          {'name': 'name9'}
      ]
  }
]

console.log(Array.from(findPath(data, ["name1", "name4", "name5"])))
// [ [ "name1", "name4", "name5" ] ]

console.log(Array.from(findPath(data, [ "name1", "name7" ])))
// [ [ "name1", "name7" ] ]

console.log(Array.from(findPath(data, [ "name1", "name9" ])))
// []

how they're the same; how they're not

Note the similarity between the two implementations and how the result is formed. Both use mutual recursion. The functional solution uses expressions whereas the generator solution uses statements. The generator implementation extends a distinct advantage where by we can chose to stop or continue iteration ("finding") whenever we want.

For example, imagine an input where there are ten (10) unique paths for the given input. Perhaps we want to just return the first match,

const findFirst = (tree = [], names = []) =>
{ for (const path of findPath(tree, names))
    return path
}

Or get the first three (3) matches -

const findFirst3 = (tree = [], names = []) =>
{ const r = []
  for (const path of findPath(tree, names))
    if (r.length < 3)
      r.push(path)
  return r
}

Or get the first N -

const findFirstN = (tree = [], names = [], n = 0) =>
{ const r = []
  for (const path of findPath(tree, names))
    if (r.length < n)
      r.push(path)
  return r
}

Generators are flexible like this. By contrast, the flatMap implementation is eager and always returns all results.



来源:https://stackoverflow.com/questions/57680671/find-path-in-tree-by-direct-path

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!