During a recent, mock technical interview, I was given the following problem to solve.
Write a function that takes in two inputs, an array, and a number. Remove the first item in the array. Then, move it to the last place in the array. Repeat this the number of times as given in the second argument and return the array.
For example, if the given array is [1,2,3]
, and the number of times it should shuffle is 1, the function should return [2,3,1]
.
Break Down the Problem
Given the problem, I thought about the main components (or problems) I would need to make things work.
- Remove the first item in the area (the item at place
array[0]
) - Take the removed item and place is at the end of the array (it becomes the item at place
array[array.length -1]
. - Repeat the above for each time the second argument specified
- Return the shuffled array
Knowing that the act of removing an item and placing it into the array would have to happen x numbers of times, a for
loop seemed like a good place to start. Within that loop I could remove the first item in the array with .shift()
then add it back onto the end of the array with .push()
.
Putting that together into a function, our solution could look like this:
function moveToEnd(array, number) {
for ( let i = 0; i < number; i++ ) {
let itemToMove = array.shift()
array.push(itemToMove)
}
return array
}
Given the following inputs:
moveToEnd([1,2,3], 1)
moveToEnd([1,2,3], 2)
moveToEnd([1,2,3], 3)
We get the following outputs:
[2,3,1]
[3,1,2]
[1,2,3]
It works!
While this solution does work, it may not be the best way to approach the problem. Let’s think about some possible edge cases that we have not accounted for.
Run Time
In the sample code, I have three simple examples of what the inputs and outputs could look like. The array is relatively small, so have been the number of times the for
loop needs to run.
In Javascript, an array can be any size. Shuffling through an array of 1000 items would take longer than shuffling through an array of 3 items. An array of 1,000,000 items would take even longer. Alternatively, if a 3 item array needed to be shuffled 1,000,000 times, it would take longer than removing an item from the front and placing it on the end 3 times.
Time complexity, runtime, and Big O notation are some of the concepts in computer science surrounding the idea of how fast, slow, or performant a program or function is. These concepts are too complex to cover in this blog post, however, we can keep the idea of runtime in mind as we refactor the moveToEnd
function.
Noticing Patterns
Focusing on one aspect of the function, the user inputs, we can improve our solution a little bit.
Going back to our earlier examples, let’s shuffle the [1,2,3]
array a few more times.
moveToEnd([1,2,3], 1) // expected return [2,3,1]
moveToEnd([1,2,3], 2) // expected return [3,1,2]
moveToEnd([1,2,3], 3) // expected return [1,2,3]
moveToEnd([1,2,3], 4) // expected return [2,3,1]
moveToEnd([1,2,3], 5) // expected return [3,1,2]
moveToEnd([1,2,3], 6) // expected return [1,2,3]
You may see a pattern here.
If the array is shuffled 1 time or 4 times, the resulting array ([2,3,1]
) is the same. Each time number
is incremented by 1, the resulting array is the same as every 3rd instance. Another interesting thing is that when the array is shuffled 3 times (or a multiple of 3 with 0 as a remainder) the resulting array is the same as the original array.
This is interesting to think about.
If the return of a function is the same when we shuffle the array 1 time, 3 times or 6,000,000 times, do we really need our for
loop to run 6,000,000 times?
No.
We can make our code a little more performant by simplifying our number
to be as small as possible in comparison to the array
then running the for
loop that many times.
Here are some scenarios to think about:
- If number is less than then the length of the
array
run the for loop - If number is greater than length of the
array
, divide it by the length of thearray
, then use the remainder to run the for loop
There are a few ways to tackle this, but for the purpose of this example, I’ll use an if statement to evaluate the value of the number argument.
if (number > array.length) {
number = number % array.length
}
On the second line, we use the modulus operator to return the remainder and assign it to the number
variable, that is then used in the for
loop.
Our updated function now looks like this.
function moveToEnd(array, number) {
if (number > array.length) {
number = number % array.length
}
for ( let i = 0; i < number; i++ ) {
let itemToMove = array.shift()
array.push(itemToMove)
}
return (array)
}
With this addition, our for
loop will run a relatively small number of times compared to what the number
argument could be.
What else?
The moveToEnd
problem is relatively easy to solve on purpose. Solving problems as an engineer is part of getting to a solution that works. And part getting to a solution that scales, accounts for edge cases or has minimal trade-offs or side effects.
In technical interviews, you may not have all the answers to what inputs could look like, what outputs should be, or other factors to consider. But, that’s on purpose. It’s on you, as the developer, to ask clarifying questions to help guide you to an optimal solution.
For example, when I was posed with this problem, I asked if it was ok to mutate the original array, or if I should return a new, shuffled array, leaving the original intact. If I was approaching this problem now, I may think about if my solution works with a nested array. Or, to account for if the number
argument is not an integer or less is less than 0. Going a little deeper, I may revisit the for loop to see if there is a better option.
Conclusion
Again, moving items in an array may not be a scenario you will encounter in the real world. But, thinking about making your code efficient is something you will encounter. Challenge yourself to revisit code you have written and refactor it to account for less-than-ideal user inputs, time complexity any other edge cases you can think of.
Resources
- https://en.wikipedia.org/wiki/Edge_case
- https://stackoverflow.com/questions/30135113/shifting-a-javascript-array-one-spot-to-the-left-without-using-the-shift-met
- https://codepen.io/scrabill/pen/dyMoovm?editors=0011
- bigocheatsheet.com
- https://www.freecodecamp.org/news/time-complexity-of-algorithms/
- https://www.lavieencode.net/podcast/029-7-steps-to-solving-code-challenges/
Photo by Olav Ahrens Røtne
The post Approaching Problems like a Software Engineer appeared first on Shannon Crabill — Software Engineer && Email Developer.
Top comments (2)
Hi,
Nice post!
That's true: you need to split big problems into smaller ones and focus on these one by one. Anyway, I think there's a way to do it better (without using loops):
I set up a fiddle so you can try it.
P.S.: This also works if the array or the positions are undefined.
Nice! I briefly remembered slicing was an option toward the end of writing this draft. This seems like it would also be more performant than a loop for sure.