Search code examples
javascriptalgorithmgraph-theorydepth-first-search

Iterative depth-first traversal with remembering value's paths


I need help with specific implementation of iterative depth first traversal algorithm. I have an object like this (it's just an example, object might have more properties and be deeper nested):

const root = {
  a: 1,
  b: {
    c: {
      d: {
        e: 2,
        f: 3,
      }
    },
    g: [
      {
        h: 4,
        i: 5,
      },
      {
        j: 6,
        k: 7,
      }
    ]
  }
}

What I need is a function that would traverse the whole object and return an array like this:

[
  {"a": 1},
  {"b.c.d.e": 2},
  {"b.c.d.f": 3},
  {"b.g.0.h": 4},
  {"b.g.0.i": 5},
  {"b.g.1.j": 6},
  {"b.g.1.k": 7},
]

I managed to create an algorithm that sort of solves my problem, but needs one additional step in the end. Result of the algorithm is an array of strings like that:

[
  'a^1',
  'b.c.d.e^2',
  'b.c.d.f^3',
  'b.g.0.h^4',
  'b.g.0.i^5',
  'b.g.1.j^6',
  'b.g.1.k^7'
]

so in order to achieve what I want I have to do one full iteration over the result of my algorithm, split strings by ^ symbol and then create objects based on that.

This is the part that I need help with - how can I improve/change my solution so I don't need to do that last step?

function dft(root) {
  let stack = [];
  let result = [];
  const isObject = value => typeof value === "object";
  stack.push(root);
  while (stack.length > 0) {
    let node = stack.pop();
    if (isObject(node)) {
      Object.entries(node).forEach(([childNodeKey, childNodeValue]) => {
        if (isObject(childNodeValue)) {
          const newObject = Object.fromEntries(
            Object.entries(childNodeValue).map(([cnk, cnv]) => {
              return [`${childNodeKey}.${cnk}`, cnv];
            })
          );
          stack.push(newObject);
        } else {
          stack.push(`${childNodeKey}^${childNodeValue}`);
        }
      })
    } else {
      result.push(node);
    }
  }
  return result.reverse();
}

Solution

  • I'd keep pairs <keys,value> in the stack and only create a string key when storing a newly created object:

    function dft(obj) {
        let stack = []
        let res = []
    
        stack.push([[], obj])
    
        while (stack.length) {
            let [keys, val] = stack.pop()
    
            if (!val || typeof val !== 'object') {
                res.push({
                    [keys.join('.')]: val
                })
            } else {
                Object.entries(val).forEach(p => stack.push([
                    keys.concat(p[0]),
                    p[1],
                ]))
            }
        }
    
        return res.reverse()
    }
    
    //
    
    const root = {
      a: 1,
      b: {
        c: {
          d: {
            e: 2,
            f: 3,
          }
        },
        g: [
          {
            h: 4,
            i: 5,
          },
          {
            j: 6,
            k: 7,
          }
        ]
      }
    }
    
    console.log(dft(root))