|
/* |
|
apply all tag from tags to the string s |
|
const tags = [ |
|
[0, 4, 'i'], // start at index, end at index, style |
|
[6, 9, 'b'], |
|
[7, 10, 'u'] |
|
] |
|
const s = 'Hello World!' |
|
return html string: <i>Hell</i>o <b>W<u>orl</u></b><u>d</u>! |
|
At one index there will be only one open or close; |
|
Tag does not need to be valid |
|
*/ |
|
|
|
function wrapTags(tags, s) { |
|
const openAt = new Map(); |
|
const closeAt = new Map(); |
|
for (const [open, close, tag] of tags) { |
|
openAt.set(open, tag); |
|
closeAt.set(close, tag); |
|
} |
|
const html = []; |
|
const openStack = []; |
|
for (let i = 0; i < s.length; i += 1) { |
|
if (openAt.has(i)) { |
|
const openTag = openAt.get(i); |
|
openStack.push(openTag); |
|
html.push(`<${openTag}>`); |
|
} |
|
html.push(s[i]); |
|
if (closeAt.has(i)) { |
|
const closeTag = closeAt.get(i); |
|
const tmpStack = []; |
|
while (openStack.length > 0 && openStack[openStack.length - 1] !== closeTag) { |
|
const tmpStyle = openStack.pop(); |
|
html.push(`</${tmpStyle}>`); |
|
tmpStack.push(tmpStyle); |
|
} |
|
if (openStack.length > 0 && openStack[openStack.length - 1] === closeTag) { |
|
html.push(`</${closeTag}>`); |
|
openStack.pop(); |
|
} |
|
while (tmpStack.length > 0) { |
|
const tmpStyle = tmpStack.pop(); |
|
html.push(`<${tmpStyle}>`); |
|
openStack.push(tmpStyle); |
|
} |
|
} |
|
} |
|
while (openStack.length > 0) { |
|
const closeTag = openStack.pop(); |
|
html.push(`</${closeTag}>`); |
|
} |
|
return html.join(''); |
|
} |
|
|
|
/* |
|
Time complexity: |
|
n = s.length |
|
m = tags.length |
|
h = total nested tag which maximum is n / 2 |
|
O(n*h) = O(n*n/2) ~ O(n ^ 2) . :( |
|
Space complexity: O(m + n) |
|
O(m) to store ranges |
|
O(html.length) ~= O(n + m) to store result (which should not count...) |
|
O(n/2) to store openStack (most nested tag can be open at the same time): |
|
*/ |
|
|
|
/* Test cases */ |
|
const s = 'Hello World!'; |
|
console.assert(wrapTags([], s) === s, "Empty styles not working"); |
|
console.assert(wrapTags([ |
|
[0, 0, 'i'], |
|
[1, 1, 'b'], |
|
[2, 2, 'u'] |
|
], s) === '<i>H</i><b>e</b><u>l</u>lo World!', "Non overlap"); |
|
console.assert(wrapTags([ |
|
[0, 11, 'i'], |
|
[1, 10, 'b'], |
|
[2, 9, 'u'] |
|
], s) === '<i>H<b>e<u>llo Worl</u>d</b>!</i>', "Contained"); |
|
console.assert(wrapTags([ |
|
[0, 11, 'i'], |
|
[1, 10, 'b'], |
|
[2, 9, 'u'], |
|
[3, 8, 'i'], |
|
[4, 7, 'b'], |
|
[5, 6, 'c'], |
|
], s) === '<i>H<b>e<u>l<i>l<b>o<c> W</c>o</b>r</i>l</u>d</b>!</i>', "Contained 2"); |
|
console.assert(wrapTags([ |
|
[0, 5, 'i'], |
|
[1, 7, 'i'] |
|
], s) === '<i>H<i>ello </i>Wo</i>rld!', "Overlap same"); |
|
console.assert(wrapTags([ |
|
[0, 5, 'i'], |
|
[1, 6, 'u'], |
|
[2, 7, 'b'], |
|
[3, 8, 'a'] |
|
], s) === '<i>H<u>e<b>l<a>lo </a></b></u></i><u><b><a>W</a></b></u><b><a>o</a></b><a>r</a>ld!', "Complex 1"); |
|
console.assert(wrapTags([ |
|
[0, 5, 'a'], |
|
[1, 6, 'b'], |
|
[2, 7, 'c'], |
|
[3, 8, 'd'], |
|
[4, 9, 'e'], |
|
[5, 10, 'f'], |
|
[6, 11, 'g'] |
|
], s) === '<a>H<b>e<c>l<d>l<e>o<f> </f></e></d></c></b></a><b><c><d><e><f><g>W</g></f></e></d></c></b><c><d><e><f><g>o</g></f></e></d></c><d><e><f><g>r</g></f></e></d><e><f><g>l</g></f></e><f><g>d</g></f><g>!</g>', "Complex 2"); |
Not sure the produced html is optimal or not.