DEV Community

loading...
Cover image for Snail Array Challenge Solution JavaScript

Snail Array Challenge Solution JavaScript

hellodevworldblog profile image Hello Dev World Blog Originally published at hellodevworld.com Updated on ・7 min read

Day 5 of 365 days of coding!

Link to the video

Today we are borrowing a challenge from Codewars!

Disclaimer: This is not my challenge the original challenge is linked about. Also, there are MANY ways to solve this problem. These are a few answers that I wrote or find clever with explanations of why/how they work

TLDR: explanation of best solution at the bottom of the post and actual solutions at the bottom of each section

The Problem

Create a function that accepts and array. Given an n x n array, return the array elements arranged from outermost elements to the middle element, traveling clockwise.

Examples:

        snail( [[1,2,3], [4,5,6],[7,8,9]]) // [1,2,3,6,9,8,7,4,5] 
        snail([[1,2,3], [8,9,4], [7,6,5]]) // [1,2,3,4,5,6,7,8,9]
        snail([[1,2,3,1], [4,5,6,4], [7,8,9,7], [7,8,9,7]]) // [1,2,3,1,4,7,7,9,8,7,7,4,5,6,8,9]
Enter fullscreen mode Exit fullscreen mode

The image below may help. You can also go to the Codewars page for more information and to test out your solution

Alt Text

Solutions

So lets break down some possible solutions

possibility 1

* create a variable for the final array

* loop through the parent array - while array still has items in it

    * get the first row (first array in the array)

    * get the items at the end of each array (right side) 

    * get the bottom row from end to front (bottom row reversed)

    * get the items at the beginning of the arrays (left side)
Enter fullscreen mode Exit fullscreen mode

possibility 2

* create a variable for the final array

* loop through the parent array - while array still has items in it

    * get the fist row

    * get the last item in each array

    * reverse the parent array and each array in the parent array
Enter fullscreen mode Exit fullscreen mode

Solution 1 - Readability and Performance

First we need to create out function that accepts an array

const snail = (arr) => {
  //create a variable for the final array
  //loop through the parent array - while array still has items in it
    //get the first row (first array in the array)
    //get the items at the end of each array (right side) 
    //get the bottom row from end to front (bottom row reversed)
    //get the items at the beginning of the arrays (left side)
}
Enter fullscreen mode Exit fullscreen mode

We have to create variable to push everything into to get our final array. We will instantiate this as an empty array that everything will get added to and if there is nothing to add it to we will return the [] at the end as expected

const snail = (arr) => {
  let finalArray = []
  //loop through the parent array - while array still has items in it
    //get the first row (first array in the array)
    //get the items at the end of each array (right side) 
    //get the bottom row from end to front (bottom row reversed)
    //get the items at the beginning of the arrays (left side)
}
Enter fullscreen mode Exit fullscreen mode

Now we need to add a while loop. If you are not familiar with them check out this MDN page. We need to do a while loop here because we don’t know how many times the loop is going to have to go through to get the final answer. We are going to make the loop while array has a length as we will be removing items from each array as we loop. In the end the array will have nothing left in it so the length will be 0 and that is when we will end to loop execution.

const snail = (arr) => {
  let finalArray = []
  while (arr.length){
    //get the first row (first array in the array)
    //get the items at the end of each array (right side) 
    //get the bottom row from end to front (bottom row reversed)
    //get the items at the beginning of the arrays (left side)
  }
}
Enter fullscreen mode Exit fullscreen mode

We want to get the first array in the array of arrays (the first row) if you don’t know how .shift(), .push(), or the spread operator works check out this MDN page but basically it takes the first item in an array.

const snail = (arr) => {
  let finalArray = []
  while (arr.length){
    finalArray.push(...arr.shift())
    //get the items at the end of each array (right side) 
    //get the bottom row from end to front (bottom row reversed)
    //get the items at the beginning of the arrays (left side)
  }
}
Enter fullscreen mode Exit fullscreen mode

We need to get all the items at the end of each array (the right side) if you are unfamiliar with for loops or .pop() check out the links on each one before continuing.

const snail = (arr) => {
  let finalArray = []
  while (arr.length){
    finalArray.push(...array.shift())
    for (var i = 0; i < arr.length; i++) {
        finalArray.push(arr[i].pop());
    }
    //get the bottom row from end to front (bottom row reversed)
    //get the items at the beginning of the arrays (left side)
  }
}
Enter fullscreen mode Exit fullscreen mode

now we need to get the last array and reverse it (bottom line) if you are unfamiliar with .reverse() check out this MDN page before continuing. you will notice that I am giving it an empty array just in case there is no last array or nothing is there the .reverse will error if it doesn’t have at least an empty array so we are giving it an empty array if array doesn’t have something to pop.

const snail = (array) =>{
  let finalArray = []
  while(array.length){
    finalArray.push(...array.shift())
    for (var i = 0; i < array.length; i++){
      finalArray.push(array[i].pop())
    }
    finalArray.push(...(array.pop() || []).reverse())
    //get the items at the beginning of the arrays (left side)
  }
}
Enter fullscreen mode Exit fullscreen mode

All we have left is to get the left side so we need to get all of the first items from each array. You will notice that i is going to be the length of the array -1 because we don’t want to grab the first array. we will grab that in the next loop so we only want the first numbers from each array before the first one.

const snail = (array) =>{
  let finalArray = []
  while(array.length){
    finalArray.push(...array.shift())
    for (var i = 0; i < array.length; i++){
      finalArray.push(array[i].pop())
    }
    finalArray.push(...(array.pop() || []).reverse())
    for (var i = array.length -1; i >= 0; i--){
      finalArray.push(array[i].shift())
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Last but not least, we return that finalArray that we have been building

const snail = (array) =>{
  let finalArray = []
  while(array.length){
    finalArray.push(...array.shift())
    for (var i = 0; i < array.length; i++){
      finalArray.push(array[i].pop())
    }
    finalArray.push(...(array.pop() || []).reverse())
    for (var i = array.length -1; i >= 0; i--){
      finalArray.push(array[i].shift())
    }
  }
  return finalArray
}
Enter fullscreen mode Exit fullscreen mode

Solution 2 - Clever and Less Lines of Code

This solution is one everyone loves on Codewars it is less performant and I don’t like the readability of it but it is less lines of code and very clever so I thought I would share it with you.

The first few lines are the same as the first solution

const snail = (arr) => {
  var finalArray = [];
  while (arr.length) {
    finalArray.push(...arr.shift());
    //get the last item in each array
    //reverse the parent array and each array in the parent array
  }
}
Enter fullscreen mode Exit fullscreen mode

The next part is very similar to the first solution and you can actually switch our the first for loop for this but we are going to use .map() to get the last number from each array (row) and push it into the finalArray

const snail = (arr) => {
  var finalArray = [];
  while (arr.length) {
    finalArray.push(...arr.shift());
    arr.map(row => finalArray.push(row.pop()))
    //reverse the parent array and each array in the parent array
  }
}
Enter fullscreen mode Exit fullscreen mode

This is where it gets really interesting. Instead of getting the bottom row reversed now we are going to reverse the whole array and each item in the arrays within the parent array and do all the same logic. This will flip everything so instead of getting the top row we are getting the bottom and instead of getting the right side we are getting the left. Then the loop continues

const snail = (arr) => {
  var finalArray = [];
  while (arr.length) {
    finalArray.push(...arr.shift());
    arr.map(row => finalArray.push(row.pop()))
    arr.reverse().map(row => row.reverse());
  }
}
Enter fullscreen mode Exit fullscreen mode

At the end we return our finalArray and TA DA! you have your solution

const snail = (arr) => {
  var finalArray = [];
  while (arr.length) {
    finalArray.push(...arr.shift());
    arr.map(row => finalArray.push(row.pop()))
    arr.reverse().map(row => row.reverse());
  }
  return finalArray
}
Enter fullscreen mode Exit fullscreen mode

pretty cool huh?

Conclusion

The second solution is pretty clever and has less lines but it is harder to read and it is not as performant. below is the jsbench performance results using the examples above for anyone who is interested.
Alt Text
I hope you had fun with this one! Please leave your solutions that you came up with in the comments section. If you have any challenge you would like to see done also leave that in the comments below you may see it come up! If you would like to get the challenge emailed to you every day in morning and a notification when the solution is posted subscribe here.

Discussion (3)

pic
Editor guide
Collapse
mellen profile image
Matt Ellen

While I don't think I have an efficient way of generating the spiral, I learnt a thing or two.
My method is to rotate the grid by 90 degrees and take the top row from the grid and add it to the output.

(function(input)
{
  input = input.flat();

  function indexToXY(index, width)
  {
    let x = index % width;
    let y = Math.floor(index / width);
    return [x,y];
  }

  function xyToIndex(x, y, width)
  {
    return (x + (y * width));
  }

  function snail(grid, width, shrinkwidth)
  {
    if(grid.length == 0)
    {
      return [];  
    }
    if(grid.length == 1)
    {
      return grid;
    }
    let part = grid.splice(0, width);
    width = Math.ceil(Math.sqrt(grid.length));
    grid = grid.reduce((oup, item, index) => 
    {
      let [x, y] = indexToXY(index, width);
      let pole = Math.floor(width/2);
      let polex = x - pole;
      let poley = y - pole;
      let r = Math.sqrt((polex*polex)+(poley*poley));
      let fi = Math.atan2(poley, polex);
      fi -= Math.PI/2;
      let newx = Math.round((Math.cos(fi)*r)+pole);
      let newy = Math.round((Math.sin(fi)*r)+pole);
      let newIndex = xyToIndex(newx, newy, width);
      oup[newIndex] = item;
      return oup;
    }, []).filter(x=>x);

    if(shrinkwidth)
    {
      width--;
    }

    return part.concat(snail(grid, width, !shrinkwidth));
  }

  return snail(input, Math.ceil(Math.sqrt(input.length)), true);

})([[1,2,3,1], [4,5,6,4], [7,8,9,7], [7,8,9,7]])
Enter fullscreen mode Exit fullscreen mode
Collapse
pavlikovinc profile image
George

Thx. I used so many loops in my solution at first, but your solution looks good.

But you have a little mistake in the second solution. In line where you finally reverse the whole array it shoud be arr.reverse() instead of array.reverse(). It seems you got a divergence with code from video again) Have a nice day

Collapse
hellodevworldblog profile image
Hello Dev World Blog Author

HAHA! thank you I ad it as array the first time and changed it lol thanks for the catch will update