2
\$\begingroup\$

I recently had a programming interview with a Silicon Valley tech company and wanted some different opinions on one of the problems they had me solve and how others might complete it. Essentially, given an array of objects:

const events = [ {'category': 'Bug', 'date': '26.07.22', 'resolved': false}, {'category': 'Breach', 'date': '26.07.22', 'resolved': false}, {'category': 'Bug', 'date': '24.07.22', 'resolved': true}, {'category': 'Bug', 'date': '24.07.22', 'resolved': true}, {'category': 'Bug', 'date': '24.07.22', 'resolved': false}, {'category': 'Restart', 'date': '22.07.22', 'resolved': true}, {'category': 'Breach', 'date': '21.07.22', 'resolved': false}] 

Return another array of objects to the user that list the unique category names and how many of them were found in the original array, like so:

[{category: 'Breach', count: 2}, {category: 'Bug', count: 4}, {category: 'Restart', count: 1}] 

How I managed it:

let categoryArray = []; for (let i = 0; i < events.length; i++) { categoryArray[i] = events[i].category; } categoryArray.sort(); let count = 1; let finalObjectArray = []; for (let i = 0; i < categoryArray.length; i++) { let blankObject = { category: "", count: "" }; let curr = categoryArray[i]; let next = categoryArray[i + 1]; if (curr === next) { count++; } else { blankObject.category = categoryArray[i]; blankObject.count = count; finalObjectArray.push(blankObject); count = 1; } } 
\$\endgroup\$
1
  • \$\begingroup\$@Jonah Please don't post answers in comments.\$\endgroup\$
    – Peilonrayz
    CommentedJul 26, 2022 at 17:00

3 Answers 3

1
\$\begingroup\$

The following may be more performant (less operations and memory usage), as well as easier to understand. I would say that using the sort and loop in the original code, while creative, is a bit difficult to read and doesn't take advantage of native data types and methods that could help accomplish its goals. I also might suggest adding some comments to explain the code.

const events = [ {'category': 'Bug', 'date': '26.07.22', 'resolved': false}, {'category': 'Breach', 'date': '26.07.22', 'resolved': false}, {'category': 'Bug', 'date': '24.07.22', 'resolved': true}, {'category': 'Bug', 'date': '24.07.22', 'resolved': true}, {'category': 'Bug', 'date': '24.07.22', 'resolved': false}, {'category': 'Restart', 'date': '22.07.22', 'resolved': true}, {'category': 'Breach', 'date': '21.07.22', 'resolved': false}]; // create categories object to count category items let categories = {}; events.forEach( ( eventObject ) => { if ( categories.hasOwnProperty( eventObject.category ) ) { categories[eventObject.category]++; } else { categories[eventObject.category] = 1; } } ); // convert object to array of objects (if this is really needed) categories = Object.keys( categories ).map( ( key ) => { return { 'category': key, 'count': categories[key] } } ); console.log( categories );

\$\endgroup\$
1
  • \$\begingroup\$Thanks! This makes sense, I've never used the hasOwnProperty function before so I'll look into that. The return obviously looks better before you convert back into an array of objects but that was just what the test case expected.\$\endgroup\$
    – MaWinf98
    CommentedJul 26, 2022 at 2:30
2
\$\begingroup\$

Time complexity

During coding interviews, it's important to pay attention to time complexity. It's good to assess the time complexity of an algorithm before jumping into coding, and consider alternatives that might be better, while also not forgetting about space complexity too.

The time complexity of sorting is generally \$O(n \log n)\$. Counting unique elements can be done without sorting in \$O(n)\$, at the expense of additional space to store the counts. The additional space in this example can be at most \$O(n)\$, or possibly just \$O(1)\$, depending on more details about the possible inputs. Also an interesting discussion to have with the interviewer.

Loop over array elements using for..of

When the index of array elements is not needed, it's simpler to use for..of:

for (const ev of events) { categoryArray.push(ev.category); } 

Limit variables to the minimal necessary scope

The count variable is only needed in the loop body. So it's good to declare it in the initialization statement of the for loop, to avoid the mistake of accidentally using it outside of the loop.

for (let i = 0, count = 1; i < categoryArray.length; i++) { 

Avoid unnecessary objects

Instead of:

let blankObject = { category: "", count: "" }; blankObject.category = categoryArray[i]; blankObject.count = count; finalObjectArray.push(blankObject); 

This is simpler:

finalObjectArray.push({ category: categoryArray[i], count: count }); 

Avoid type names in variable names

Including "Array" or "Object" in variable names doesn't make them easier to understand. These terms are very technical and overly specific to implementation details. It's better to use natural, simple, but descriptive terms, for example:

  • categoryArray -> categories
  • finalObjectArray -> categoryCounts

Use const

The problem description defines a const events = [ ... ]. This could have been a hint.

Variables that should not be reassigned should be declared with const, for example categoryArray.

\$\endgroup\$
    1
    \$\begingroup\$

    Here's a one liner: events.reduce((m, x) => ({ ...m, [x.category]: (m[x.category] || 0) + 1 }), {})
    Jonah, CC BY-SA 4.0, 2022-07-26 04:09:26

    \$\endgroup\$
    1
    • \$\begingroup\$If you are the OP and want to get reputation for your insight feel free to post a new answer. Then please notify a moderator to delete this answer by flagging this answer.\$\endgroup\$
      – Peilonrayz
      CommentedJul 26, 2022 at 17:00

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.