6

I am having trouble transform a string using a certain mapping. The string I want to transform is the following:

Input

$B$O$TT$O$$KK$$Z$HH$$U$PP$$QQ$U$Z$B$

Which can be better seen as the following (very close to HTML):

$B
  $O
    $TT$
  O$
  $K
  K$
  $Z
    $HH$
    $U
      $PP$
      $QQ$
    U$
  Z$
B$

I am aiming to transform this into the following:

Expected Result

{
  "v": "B",
  "chld": [
    {
      "v": "O",
      "chld": [
        {
          "v": "T"
        }
      ]
    },
    {
      "v": "K"
    },
    {
      "v": "Z",
      "chld": [
        {
          "v": "H"
        },
        {
          "v": "U",
          "chld": [
            {
              "v": "P"
            },
            {
              "v": "Q"
            }
          ]
        }
      ]
    }
  ]
}

Heres where I have gotten:

function transform(contentString, currentObj, currentChld) {
  let closeOpen = {
    open: /\$[A-Z]/i,
    closed: /[A-Z]\$/i 
  }

  if (!contentString.length) {
    return currentObj
  }

  let currentSlice = contentString.slice(0, 2)
  let newString = contentString.substring(2)
  //See if tag is open, for example $A is open, A$ is closed
  if (currentSlice.match(closeOpen['open'])){
    currentObj = {v: currentSlice[1]}
    currentObj.chld = []
    return transform(newString,  currentObj , currentChld)

  }

}

It seems the recursion is not quite kicking in. Solution doesnt have to be recursive. If theres something that simpler thats okay too! My hope is to get the expected result above. Can anyone help? Simplest solution would be best!

EDIT: yes tags will only have one letter, restricted to thos in [A-Z]

4
  • 1
    "It seems the recursion is not quite kicking in." - What is that supposed to mean? What is your question? Commented Jan 27, 2018 at 22:49
  • Are tags guaranteed to be only one character? Commented Jan 27, 2018 at 22:53
  • @PatrickRoberts that is correct, made edit Commented Jan 27, 2018 at 22:55
  • What do you expect currentChild to be? (E.g. what type? Is it an array? You never use it.) Commented Jan 27, 2018 at 23:15

3 Answers 3

4

I'm late to the party, but a simple stack based appraoch works fine.

var str = "$B$O$TT$O$$KK$$Z$HH$$U$PP$$QQ$U$Z$B$"

function convert(str) {
  var nodes = str.match(/../g)

  var node, parent, stack = [{
    chld: []
  }];

  for (key of nodes) {
    parent = stack.slice(-1)[0]
    if (key[0] == "$") {
      node = {
        v: key[1],
        chld: []
      }
      parent.chld.push(node);
      stack.push(node)
    } else if (key[1] == "$") {
      node = stack.pop()
      if (!node.chld.length) delete node.chld
    }

  }
  return stack.pop().chld[0]
}
console.log(convert(str));

Sign up to request clarification or add additional context in comments.

5 Comments

Can you help me understand why you are doing parent = stack.slice(-1)[0]?
@me_man Of course, it is in order to access the last element on the stack. It's essentially the same as doing parent = stack [stack.length - 1]. You want to get the parent element without popping it from the stack, because you still want to add more children to it.
I like your answer because its bare JS, but after tracing through, I can't quite understand the purpose of the stack. I get that a stack is a LIFO structure, but how is it helping in this situation?
@me_man It is helping in creating the nested structure. It is simply easy to follow and easy to create the desired structure like this. The element you're working with is always the top of the stack. If you need to go a level deeper, you just push a new element on the stack.To go a level up, you just pop the last. No need to wrap your mind through recursion or nested structures. It's an easy way to keep track of the object you're working with. The stack, seen over time, resembles much of the structure of the nested object you end up with, so it's a good fit.
@me_man Try to visualize the pretty printed input of your question as if it were lying sideways. It already looks like a stack where only the top element is visible. Like an array you're pushing to and popping out again. I don't know how to explain it better, it just seems intuitive to do it like that.
1

You can write a PEG.js parser to create the tree for you like this:

class Node {
  constructor (tag, children = []) {
    this.v = tag;
    if (children.length > 0) {
      this.chld = children;
    }
  }
  toString () {
    return '$' + this.v + (this.chld || []).join('') + this.v + '$'
  }
}

let parser = PEG.buildParser(`
Expression = open:Open children:Expression * close:Close {
  var object = new Node(open, children);

  if (open !== close) {
    return expected('"' + object + '"');
  }

  return object
}

Open = "$" tag:Tag { return tag } 
Close = close:Tag "$" { return close }
Tag = [A-Za-z]
`, { cache: true });

let tree = parser.parse('$B$O$TT$O$$KK$$Z$HH$$U$PP$$QQ$U$Z$B$');

console.log(tree);

try {
  parser.parse('$B$O$TT$$U$PP$V$O$B$');
} catch (error) {
  console.log(error.message);
}
.as-console-wrapper{max-height:100%!important}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pegjs/0.9.0/peg.min.js"></script>

Note that in version 0.10, the method has been changed from peg.buildParser() to peg.generate() according to the documentation.

2 Comments

good stuff! In a real world app they can probably generate the parser in their build script and then simply include it.
Explanation for random downvote would be much appreciated.
0

Here's a simple loop to build the structure as a string, then use the JSON parser to turn it into an object:

const ip = "$B$O$TT$O$$KK$$Z$HH$$U$PP$$QQ$U$Z$B$"

function transform(contentString) {
    let op = "";
    for(var i = 0; i < contentString.length - 3; i += 2) {
        if (contentString[i] === "$") {
            op += `{"v":"${contentString[i+1]}"`;
            if (contentString[i+2] === "$") {
                op += `,"chld":[`;
            }
        } else {
            op += "}";
            if (contentString[i+2] === "$") {
                op += ",";
            } else {
                op += "]";
            }
        }
    }
    return JSON.parse(op + "}");
}

console.log(transform(ip));

Note there's no syntax checking; the code assumes that the input string is a well-formed structure.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.