Home > Blockchain >  Recursive approach for building a tree like object hierarchy does not work as expected
Recursive approach for building a tree like object hierarchy does not work as expected

Time:05-10

I'm trying to write functionality something like this:

Input:

['A', '-B1', '--B2', '-C1', 'D']

Output:

{'A': {'B1': {'B2': {}}, 'C1': {}}, 'D': {}}

As you can see that B1 and C1 are children of A and B2 is a child of B1. It can go any level nested based on -

I wrote a Javascript code for the same but it seems something wrong when children are created. Here is my code:

function fetch_levels(li, chr='-') {
    var res_list = []
    for (i=0; i<li.length; i  ) {
        item = li[i]
        level = item.length - item.replace(RegExp(`^${chr} `), '').length
        key = item.replace(RegExp(`^${chr} `), '')
        value = {}
        res_list.push({
            level: level,
            name: key,
            value: value
        })
    }
    return res_list
}

function ttree_to_json(ttree, level=0) {
    result = {}
    for (i = 0; i < ttree.length; i  ) {
        cn = ttree[i]
        nn = ttree[i 1] || {'level': -1}

        // Edge cases
        if (cn['level'] > level) {
            continue
        }
        if (cn['level'] < level) {
            return result
        }

        // Recursion
        if (nn['level'] == level) {
            result[cn['name']] = cn['value']
        } else if (nn['level'] > level) {
            rr = ttree_to_json(ttree.slice(i 1), level=nn['level'])
            result[cn['name']] = rr
        }
        else {
            result[cn['name']] = cn['value']
            return result
        }
    }
    return result
}

And this is how the functions can be invoked:

console.log(ttree_to_json(fetch_levels(['A', '-B1', '--B2', '-C', 'D'])))

And the output I got is something like:

Object { B2: {…}, C: {} }

Which is wrong.

Note: Same logic is applied in the python program and it seems to be giving the correct output. I'm not able to figure out what is going wrong in JavaScript. For reference, I'm mentioning the working version of the Python program as well:

def fetch_levels(li: list, split_char='-'):
    res_list = []
    for item in li:
        level = len(item) - len(item.lstrip(split_char))
        key = item.lstrip(split_char)
        val = {}
        res_list.append({'level': level, 'name': key, 'value': val})
    return res_list


def ttree_to_json(ttree, level=0):
    result = {}
    for i in range(0, len(ttree)):
        cn = ttree[i]
        try:
            nn = ttree[i 1]
        except:
            nn = {'level': -1}

        # Edge cases
        if cn['level'] > level:
            continue
        if cn['level'] < level:
            return result

        # Recursion
        if nn['level'] == level:
            result[cn['name']] = cn['value']
        elif nn['level'] > level:
            rr = ttree_to_json(ttree[i 1:], level=nn['level'])
            result[cn['name']] = rr
        else:
            result[cn['name']] = cn['value']
            return result
    return result


if __name__ == '__main__':
    ttree = fetch_levels(['A', '-B1', '--B2', '-C1', 'D'])
    result = ttree_to_json(ttree)
    print(result)

Can any help me to figure out what is wrong with the JavaScript code?

The solution is inspired by the sample code mentioned here:

CodePudding user response:

You could simplify the code by taking an array for the level references of nested objects.

Any property is assigned to the given level (count of dashes) and it takes object to the next level.

const
    data = ['A', '-B1', '--B2', '-C1', 'D'],
    result = {},
    levels = [result];
    
data.forEach(s => {
    let level = 0;
    while (s[level] === '-') level  ;
    s = s.slice(level);
    levels[level][s] = levels[level   1] = {};
});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

CodePudding user response:

There is no recursion needed. Since the only reliable information about the to be added (next) nesting level is carried by the dashes count, one can add the next level anyway just to the object reference of the most recently processed previous/lower level.

Thus a lookup in form of an array which refers by its index to each recently created level/branch is everything needed for a simple loop based solution.

The next provided implementation uses a reduce based approach.

function aggregateObjectHierarchyLevel(
  { recentLevels, result = {} },
  item,
  idx,
) {
  // handle/synchronize the initial `result` reference.
  if (idx === 0) {
    recentLevels = [result];
  }
  const [_, dashes = '', key = ''] = item.match(/^([-]*)(.*)/) ?? [];
  const level = dashes.length;

  recentLevels[level   1] = recentLevels[level][key] = {};

  return { recentLevels, result };
};


console.log(
  ['A', '-B1', '--B2', '-C1', 'D']
    .reduce(aggregateObjectHierarchyLevel, {})
    .result
)
console.log(
  ['A', '-B1', '--B2', '---B3', '-C1', 'D']
    .reduce(aggregateObjectHierarchyLevel, {})
    .result
)
console.log(
  ['A', '-B1', '--B2', '---B3', '--C2', '---C3', '-D1', 'E']
    .reduce(aggregateObjectHierarchyLevel, {})
    .result
)
.as-console-wrapper { min-height: 100%!important; top: 0; }

CodePudding user response:

Another version, quite similar in design to both Peter's and Nina's, but with a different coding style, looks like this:

const nest = (strings, result = {}) => strings .reduce (
  (levels, str, _, __, [___, hyphens, val] = str .match (/(\-*)(. )/), lvl = hyphens .length) => (
    (levels [lvl] [val] = levels [lvl   1] = {}), 
    levels
  ), [result]
) [0]

console .log (nest (['A', '-B1', '--B2', '-C1', 'D']))

We use the same nesting structure as both those answers, and use a similar regex as Peter to separate out the two parts of the string, and the same updating logic as in both of those answers. Here we do this in a .reduce call, with only the hidden accumulator being modified.

I wrote this independently of those, came back and saw that there were already two good answers, but thought this was enough different to include as well. (I also fixed it to steal Peter's destructuring of the regex match call. Much better than my original .slice (1, 3)-then-destructure approach!)

It's interesting to see three very different coding styles all approach the problem with the same fundamental design!

  • Related