Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_
- [Task #12](src/interview/build-tree/with-replies-sort) - _Build Tree: With Replies Sort_
- [Task #13](src/interview/build-comments-tree) - _Build Comments Tree_
111 changes: 111 additions & 0 deletions src/interview/build-comments-tree/solution.test.ts
Original file line number Diff line number Diff line change
@@ -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([]);
});
});
58 changes: 58 additions & 0 deletions src/interview/build-comments-tree/solution.ts
Original file line number Diff line number Diff line change
@@ -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<number, Tree>();
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;
};
Loading