diff --git a/README.md b/README.md index 94de8d6..f06bd83 100644 --- a/README.md +++ b/README.md @@ -131,4 +131,5 @@ Interview Questions - [Task #9](src/interview/render-items/no-async) - _Render Items: No Async_ - [Task #10](src/interview/render-items/with-async) - _Render Items: With Async_ - [Task #11](src/interview/build-tree/no-replies-sort) - _Build Tree: No Replies Sort_ -- [Task #12](src/interview/build-tree/with-replies-sort) - _Build Tree: With Replies Sort_ \ No newline at end of file +- [Task #12](src/interview/build-tree/with-replies-sort) - _Build Tree: With Replies Sort_ +- [Task #13](src/interview/build-comments-tree) - _Build Comments Tree_ \ No newline at end of file diff --git a/src/interview/build-comments-tree/solution.test.ts b/src/interview/build-comments-tree/solution.test.ts new file mode 100644 index 0000000..1e4fd6b --- /dev/null +++ b/src/interview/build-comments-tree/solution.test.ts @@ -0,0 +1,111 @@ +import { buildCommentsTree } from './solution'; + +describe('Build Comments Tree | Interview | Testcases', () => { + test('#1 Builds the comments tree from the example', () => { + const comments = [ + { id: 1, text: 'A', level: 0 }, + { id: 2, text: 'B', level: 1 }, + { id: 3, text: 'C', level: 2 }, + { id: 4, text: 'D', level: 1 }, + { id: 5, text: 'E', level: 0 }, + ]; + + expect(buildCommentsTree(comments)).toEqual([ + { + id: 1, + text: 'A', + level: 0, + children: [ + { + id: 2, + text: 'B', + level: 1, + children: [{ id: 3, text: 'C', level: 2 }], + }, + { id: 4, text: 'D', level: 1 }, + ], + }, + { id: 5, text: 'E', level: 0 }, + ]); + }); + + test('#2 Uses the last previous comment from the parent level', () => { + const comments = [ + { id: 1, text: 'First root', level: 0 }, + { id: 2, text: 'First child', level: 1 }, + { id: 3, text: 'Second root', level: 0 }, + { id: 4, text: 'Second child', level: 1 }, + { id: 5, text: 'Second grandchild', level: 2 }, + ]; + + expect(buildCommentsTree(comments)).toEqual([ + { + id: 1, + text: 'First root', + level: 0, + children: [{ id: 2, text: 'First child', level: 1 }], + }, + { + id: 3, + text: 'Second root', + level: 0, + children: [ + { + id: 4, + text: 'Second child', + level: 1, + children: [{ id: 5, text: 'Second grandchild', level: 2 }], + }, + ], + }, + ]); + }); + + test('#3 Supports arbitrary nesting depth', () => { + const comments = [ + { id: 1, text: 'Level 0', level: 0 }, + { id: 2, text: 'Level 1', level: 1 }, + { id: 3, text: 'Level 2', level: 2 }, + { id: 4, text: 'Level 3', level: 3 }, + ]; + + expect(buildCommentsTree(comments)).toEqual([ + { + id: 1, + text: 'Level 0', + level: 0, + children: [ + { + id: 2, + text: 'Level 1', + level: 1, + children: [ + { + id: 3, + text: 'Level 2', + level: 2, + children: [{ id: 4, text: 'Level 3', level: 3 }], + }, + ], + }, + ], + }, + ]); + }); + + test('#4 Does not mutate the source array or its comments', () => { + const comments = [ + { id: 1, text: 'Root', level: 0 }, + { id: 2, text: 'Child', level: 1 }, + ]; + const originalComments = comments.map((comment) => ({ ...comment })); + + buildCommentsTree(comments); + + expect(comments).toEqual(originalComments); + }); + + test('#5 Returns an empty array for an empty input', () => { + expect(buildCommentsTree([])).toEqual([]); + }); +}); diff --git a/src/interview/build-comments-tree/solution.ts b/src/interview/build-comments-tree/solution.ts new file mode 100644 index 0000000..91f85ce --- /dev/null +++ b/src/interview/build-comments-tree/solution.ts @@ -0,0 +1,58 @@ +interface Comment { + id: number; + text: string; + level: number; +} + +interface Tree extends Comment { + children?: Tree[]; +} + +/** + * @param comments - array of elements with id, text & level + * + * @returns array of comments with `level = 0`. + * Element with `level N` must be inserted into `children` property of last previous element with `N - 1 level`. + * `comments` array is not mutated + * + * @example + * + * const comments = [ + * { id: 1, text: 'A', level: 0 }, + * { id: 2, text: 'B', level: 1 }, + * { id: 3, text: 'C', level: 2 }, + * { id: 4, text: 'D', level: 1 }, + * { id: 5, text: 'E', level: 0 }, + * ]; + * + * console.log(JSON.stringify(buildCommentsTree(comments), null, 2)); + * + * // Expected structure: + * // A + * // ├─ B + * // │ └─ C + * // └─ D + * // E + */ +export const buildCommentsTree = (comments: Comment[]): Tree[] => { + const roots: Tree[] = []; + const commentsMap = new Map(); + for (const comment of comments) { + const node: Tree = { ...comment }; + commentsMap.set(comment.level, node); + if (comment.level === 0) { + roots.push(node); + continue; + } + + const parent = commentsMap.get(comment.level - 1); + if (!parent) { + continue; + } + + parent.children ??= []; + parent.children.push(node); + } + + return roots; +};