5

I'm looking to loop through a matrix similar to Looping in a spiral but looping outside-in, instead of inside-out. Can anyone help me with a good way to do this for a matrix of any size, ideally in Ruby?

Example: In a 3x4 matrix I want to start at [0,0] going right, then move down once I reach [3,0], move left at [3,2] etc.

[0,0] [1,0] [2,0] [3,0]
[0,1] [1,1] [2,1] [3,1]
[0,2] [1,2] [2,2] [3,2]

The order to move is shown below:

0  1  2  3
9  10 11 4
8  7  6  5

And the output would be:

[0,0], [1,0], [2,0], [3,0], [3,1], [3,2], [2,2], [1,2], [0,2], [0,1], [1,1], [2,1]
Community
  • 1
  • 1
ant
  • 171
  • 2
  • 13
  • 1
    I made a ruby method to solve this question, it's a bit verbose but should be easy to understand how it works: https://gist.github.com/hbejgel/c18b2d54888305dc768d – hbejgel Mar 03 '15 at 17:33
  • If anyone still cares about the duplicates, here they are: http://stackoverflow.com/questions/28726345/possible-algorithms-for-printing-spiral-matrix/28726589#28726589 and http://stackoverflow.com/questions/23799776/how-to-build-spiral-square-matrix-using-recursion - other than the fact that they don't give out ruby code, I find most answers in both very satisfactory for this question. – IVlad Mar 03 '15 at 18:01
  • Welcome to Stack Overflow. It's really important to show us what you've tried as you attempted to solve the problem. That allows us to correct your mistakes in your own code, instead of having to shoehorn unfamiliar/alien code into place in your existing code. That's assuming you have tried something and are not just hoping someone will write this for you. – the Tin Man Mar 03 '15 at 18:59
  • @theTinMan fair point. I was using the basic algorithm outlined here http://stackoverflow.com/questions/28726345/possible-algorithms-for-printing-spiral-matrix/28726589#28726589 but I knew that Ruby must have a much more elegant way to solve this. I'll post my code in future questions! – ant Mar 03 '15 at 23:31
  • possible duplicate of [Looping in a spiral](http://stackoverflow.com/questions/398299/looping-in-a-spiral) – dgilperez Mar 04 '15 at 00:14

4 Answers4

11

Without loss of generality, let's write the array as:

arr = [
  [ 1,  2,  3, 4,],
  [12, 13, 14, 5,],
  [11, 16, 15, 6,],
  [10,  9,  8, 7,]
]

The desired result is:

[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

I will use a helper:

def rotate_anticlockwise(arr)
  arr.map(&:reverse).transpose
end

For example:

rotate_anticlockwise(arr)
  #=> [[4,  5,  6, 7],
  #    [3, 14, 15, 8],
  #    [2, 13, 16, 9],
  #    [1, 12, 11, 10]] 

We can now compute the desired result as follows:

out = []
a = arr.map(&:dup)
while a.any?
  out.concat(a.shift)
  a = rotate_anticlockwise(a)
end
out
  # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] 
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    Thanks everyone, it's been very interesting to see your different solutions. In the end I used this solution because I like the way it uses transpose to solve it. – ant Mar 03 '15 at 23:37
  • interesting... it is like slicing the top layer and then rotate the matrix? So it is like a block and cheese and you always slice the top and then rotate the cheese block... now doesn't it make it `O(n*m*k)` where k is min(n,m) and the best method should be `O(n*m)`?... by the way, what if the question is now to "populate an n * m empty matrix in the spiral way", exactly in a way how the matrix was like in this answer? – nonopolarity Nov 20 '15 at 00:57
  • @太極者無極而生, I like your cheese block. Let me think about what you said. I'll try to reply tomorrow. – Cary Swoveland Nov 20 '15 at 09:17
5

This is a problem that has always fascinated me. @CarySwoveland has the trick that makes this really elegant in Ruby. Here it is in a one-line method:

def spiral(matrix)
  matrix.empty? ? [] : matrix.shift + spiral(matrix.transpose.reverse)
end

arr = [[ 1,  2,  3, 4,],
       [12, 13, 14, 5,],
       [11, 16, 15, 6,],
       [10,  9,  8, 7,]]

spiral(arr)

# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

One flaw of this approach, however, is that it mutates the original matrix arr:

arr

# => [[12, 13, 14, 5], [11, 16, 15, 6], [10, 9, 8, 7]]

There are several answers in this gist that are also worth a look.

O-I
  • 1,535
  • 1
  • 13
  • 13
  • Great to see a short one-liner! – ant Mar 03 '15 at 23:38
  • Nice one, O-I. I like how you eliminated my mapping of `reverse` (`matrix.reverse.transpose` also works)l and your use of recursion. If the original matrix is not to be mutated, just add `def spiral_starter(matrix); spiral(matrix.map(&:dup)); end`. You should frame your answer. – Cary Swoveland Mar 05 '15 at 02:16
2

Here is working C code that prints a 2D matrix in clockwise fashion from outside to inside.

int n = 2, m = 5;
    int arr[2][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    int top = 0, bottom = n-1, right = m-1, left = 0, i=0, j=0;

    printf("%d ", arr[i][j]);

    while(1){
        if(top>bottom || left>right) break;


        if(bottom==top){
            for(j=left; j<=right; j++)
                printf("%d ", arr[top][j]);
            break;
        }

        if(right==left){
            for(i=top; i<=bottom; i++)
                printf("%d ", arr[left][i]);
            break;
        }

        int first = 1;
        while(j<=right){
            if(first){
                j++;
                first = 0;
                continue;
            }

            printf("%d ", arr[i][j]);
            j++;
        }
        j--; right--;

        first = 1;
        while(i<=bottom){
            if(first){
                i++;
                first = 0;
                continue;
            }           

            printf("%d ", arr[i][j]);
            i++;
        }
        i--; bottom--;

        first = 1;
        while(j>=left){
            if(first){
                j--;
                first = 0;
                continue;
            }

            printf("%d ", arr[i][j]);
            j--;
        }
        j++; left++;

        first = 1;
        while(i>=top+1){
            if(first){
                i--;
                first = 0;
                continue;
            }

            printf("%d ", arr[i][j]);
            i--;
        }
        i++; top++;
    }

The logic and reasoning behind the code is that you maintain the boundaries of the matrix that hasn't been printed till now. So at the start of the procedure the top boundary=0, bottom=n-1, left=0, right=n-1.

At every iteration in the outer while loop you check if the remaining matrix defined by the boundaries degenerates into a row or column matrix. Or if all the elements of the matrix have been printed following which it breaks out of the loop.

Moreover, the 'first' variable in each inner while loop keeps track whether the value we are printing is the first value for that row/col. The first value won't be printed because it would have already been printed by the loop just before it.

Time complexity : O(n)
Space complexity : O(1)

where n is the number of elements in the array.

Nikunj Banka
  • 11,117
  • 16
  • 74
  • 112
2

This method does what you need, it loops through the outer spiral and then recursively calls itself to loop through the inner spirals

def inward_spiral(height, width, current_height, current_width)
  if height <= 0 || width <= 0
    return
  end

  # Right
  (width-1).times do
      puts "[#{current_width}, #{current_height}]"
      current_width += 1 
  end

  # Down
  (height-1).times do
    puts "[#{current_width}, #{current_height}]"
    current_height += 1
  end

  # Left
  if height > 1
      (width-1).times do 
          puts "[#{current_width}, #{current_height}]"
          current_width -= 1 
      end
  end

  # Up
  if width > 1
      (height-2).times do
          puts "[#{current_width}, #{current_height}]"
          current_height -= 1
      end
  end

  puts "[#{current_width}, #{current_height}]"
  inward_spiral(height-2, width-2, current_width + 1, current_height)
end

And then call it inward_spiral(3,4,0,0)

You can also fill up a matrix to draw the spiral if at each step you fill it up with the direction you're going to get something like this:

→  →  →  →  ↴ 
↱  →  →  ↴  ↓ 
↑  ↱  ↴  ↓  ↓ 
↑  ↑  ↓  ↓  ↓ 
↑  ↑  ↓  ↓  ↓ 
↑  ↑  ↓  ↓  ↓ 
↑  ↑  ↓  ↓  ↓ 
↑  ↑  ✓  ↓  ↓ 
↑  ↑  ←  ⤶  ↓ 
↑  ←  ←  ←  ⤶ 
hbejgel
  • 4,674
  • 2
  • 17
  • 27