Looping in a spiral

前端 未结 30 2386
独厮守ぢ
独厮守ぢ 2020-11-22 15:07

A friend was in need of an algorithm that would let him loop through the elements of an NxM matrix (N and M are odd). I came up with a solution, but I wanted to see if my fe

相关标签:
30条回答
  • 2020-11-22 15:31

    I am sharing this code which I designed for a different purpose; it is about finding the Column number "X", and the row number "Y" of array element @ spiral index "index". This function takes the width "w" and height "h" of the matrix, and the required "index". Of course, this function can be used to produce the same required output. I think it is the fastest possible method (as it jumps over cells instead of scanning them).

        rec BuildSpiralIndex(long w, long h, long index = -1)
        {  
            long count = 0 , x = -1,  y = -1, dir = 1, phase=0, pos = 0,                            length = 0, totallength = 0;
            bool isVertical = false;
            if(index>=(w*h)) return null;
    
            do 
            {                
                isVertical = (count % 2) != 0;
                length = (isVertical ? h : w) - count/2 - count%2 ;
                totallength += length;
                count++;
            } while(totallength<index);
    
            count--; w--; h--;
            phase = (count / 4); pos = (count%4);
            x = (pos > 1 ? phase : w - phase);
            y = ((pos == 1 || pos == 2) ? h - phase : phase) + (1 * (pos == 3 ? 1 : 0));
            dir = pos > 1 ? -1 : 1;
            if (isVertical) y -= (totallength - index - 1) * dir;
            else x -= (totallength - index -1) * dir;
            return new rec { X = x, Y = y };
        }
    
    0 讨论(0)
  • 2020-11-22 15:33

    I made this one with a friend that adjusts the spiral to the canvas aspect ratio on Javascript. Best solution I got for a image evolution pixel by pixel, filling the entire image.

    Hope it helps some one.

    var width = 150;
    var height = 50;
    
    var x = -(width - height)/2;
    var y = 0;
    var dx = 1;
    var dy = 0;
    var x_limit = (width - height)/2;
    var y_limit = 0;
    var counter = 0;
    
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext('2d');
    
    setInterval(function(){
       if ((-width/2 < x && x <= width/2)  && (-height/2 < y && y <= height/2)) {
           console.log("[ " + x + " , " +  y + " ]");
           ctx.fillStyle = "#FF0000";
           ctx.fillRect(width/2 + x, height/2 - y,1,1);
       }
       if( dx > 0 ){//Dir right
           if(x > x_limit){
               dx = 0;
               dy = 1;
           }
       }
       else if( dy > 0 ){ //Dir up
           if(y > y_limit){
               dx = -1;
               dy = 0;
           }
       }
       else if(dx < 0){ //Dir left
           if(x < (-1 * x_limit)){
               dx = 0;
               dy = -1;
           }
       }
       else if(dy < 0) { //Dir down
           if(y < (-1 * y_limit)){
               dx = 1;
               dy = 0;
               x_limit += 1;
               y_limit += 1;
           }
       }
       counter += 1;
       //alert (counter);
       x += dx;
       y += dy;      
    }, 1);
    

    You can see it working on http://jsfiddle.net/hitbyatruck/c4Kd6/ . Just be sure to change the width and height of the canvas on the javascript vars and on the attributes on the HTML.

    0 讨论(0)
  • 2020-11-22 15:36

    This is a slightly different version - trying to use recursion and iterators in LUA. At each step the program descends further inside the matrix and loops. I also added an extra flag to spiral clockwise or anticlockwise. The output starts from the bottom right corners and loops recursively towards the center.

    local row, col, clockwise
    
    local SpiralGen
    SpiralGen = function(loop)  -- Generator of elements in one loop
        local startpos = { x = col - loop, y = row - loop }
        local IteratePosImpl = function() -- This function calculates returns the cur, next position in a loop. If called without check, it loops infinitely
    
            local nextpos = {x = startpos.x, y = startpos.y}        
            local step = clockwise and {x = 0, y = -1} or { x = -1, y = 0 }
    
            return function()
    
                curpos = {x = nextpos.x, y = nextpos.y}
                nextpos.x = nextpos.x + step.x
                nextpos.y = nextpos.y + step.y
                if (((nextpos.x == loop or nextpos.x == col - loop + 1) and step.y == 0) or 
                    ((nextpos.y == loop or nextpos.y == row - loop + 1) and step.x == 0)) then --Hit a corner in the loop
    
                    local tempstep = {x = step.x, y = step.y}
                    step.x = clockwise and tempstep.y or -tempstep.y
                    step.y = clockwise and -tempstep.x or tempstep.x
                    -- retract next step with new step
                    nextpos.x = curpos.x + step.x 
                    nextpos.y = curpos.y + step.y
    
                end         
                return curpos, nextpos
            end
        end
        local IteratePos = IteratePosImpl() -- make an instance
        local curpos, nextpos = IteratePos()
        while (true) do
            if(nextpos.x == startpos.x and nextpos.y == startpos.y) then            
                coroutine.yield(curpos)
                SpiralGen(loop+1) -- Go one step inner, since we're done with this loop
                break -- done with inner loop, get out
            else
                if(curpos.x < loop + 1 or curpos.x > col - loop or curpos.y < loop + 1 or curpos.y > row - loop) then
                    break -- done with all elemnts, no place to loop further, break out of recursion
                else
                    local curposL = {x = curpos.x, y = curpos.y}
                    curpos, nextpos = IteratePos()
                    coroutine.yield(curposL)
                end
            end     
        end 
    end
    
    
    local Spiral = function(rowP, colP, clockwiseP)
        row = rowP
        col = colP
        clockwise = clockwiseP
        return coroutine.wrap(function() SpiralGen(0) end) -- make a coroutine that returns all the values as an iterator
    end
    
    
    --test
    for pos in Spiral(10,2,true) do
        print (pos.y, pos.x)
    end
    
    for pos in Spiral(10,9,false) do
        print (pos.y, pos.x)
    end
    
    0 讨论(0)
  • 2020-11-22 15:37

    Here's an answer in Julia: my approach is to assign the points in concentric squares ('spirals') around the origin (0,0), where each square has side length m = 2n + 1, to produce an ordered dictionary with location numbers (starting from 1 for the origin) as keys and the corresponding coordinate as value.

    Since the maximum location per spiral is at (n,-n), the rest of the points can be found by simply working backward from this point, i.e. from the bottom right corner by m-1 units, then repeating for the perpendicular 3 segments of m-1 units.

    This process is written in reverse order below, corresponding to how the spiral proceeds rather than this reverse counting process, i.e. the ra [right ascending] segment is decremented by 3(m+1), then la [left ascending] by 2(m+1), and so on - hopefully this is self-explanatory.

    import DataStructures: OrderedDict, merge
    
    function spiral(loc::Int)
        s = sqrt(loc-1) |> floor |> Int
        if s % 2 == 0
            s -= 1
        end
        s = (s+1)/2 |> Int
        return s
    end
    
    function perimeter(n::Int)
        n > 0 || return OrderedDict([1,[0,0]])
        m = 2n + 1 # width/height of the spiral [square] indexed by n
        # loc_max = m^2
        # loc_min = (2n-1)^2 + 1
        ra = [[m^2-(y+3m-3), [n,n-y]] for y in (m-2):-1:0]
        la = [[m^2-(y+2m-2), [y-n,n]] for y in (m-2):-1:0]
        ld = [[m^2-(y+m-1), [-n,y-n]] for y in (m-2):-1:0]
        rd = [[m^2-y, [n-y,-n]] for y in (m-2):-1:0]
        return OrderedDict(vcat(ra,la,ld,rd))
    end
    
    function walk(n)
        cds = OrderedDict(1 => [0,0])
        n > 0 || return cds
        for i in 1:n
            cds = merge(cds, perimeter(i))
        end
        return cds
    end
    

    So for your first example, plugging m = 3 into the equation to find n gives n = (5-1)/2 = 2, and walk(2) gives an ordered dictionary of locations to coordinates, which you can turn into just an array of coordinates by accessing the dictionary's vals field:

    walk(2)
    DataStructures.OrderedDict{Any,Any} with 25 entries:
      1  => [0,0]
      2  => [1,0]
      3  => [1,1]
      4  => [0,1]
      ⋮  => ⋮
    
    [(co[1],co[2]) for co in walk(2).vals]
    25-element Array{Tuple{Int64,Int64},1}:
     (0,0)  
     (1,0)  
     ⋮       
     (1,-2) 
     (2,-2)
    

    Note that for some functions [e.g. norm] it can be preferable to leave the coordinates in arrays rather than Tuple{Int,Int}, but here I change them into tuples—(x,y)—as requested, using list comprehension.

    The context for "supporting" a non-square matrix isn't specified (note that this solution still calculates the off-grid values), but if you want to filter to only the range x by y (here for x=5,y=3) after calculating the full spiral then intersect this matrix against the values from walk.

    grid = [[x,y] for x in -2:2, y in -1:1]
    5×3 Array{Array{Int64,1},2}:
     [-2,-1]  [-2,0]  [-2,1]
       ⋮       ⋮       ⋮ 
     [2,-1]   [2,0]   [2,1]
    
    [(co[1],co[2]) for co in intersect(walk(2).vals, grid)]
    15-element Array{Tuple{Int64,Int64},1}:
     (0,0)  
     (1,0)  
     ⋮ 
     (-2,0) 
     (-2,-1)
    
    0 讨论(0)
  • 2020-11-22 15:37

    C# version, handles non-square sizes as well.

    private static Point[] TraverseSpiral(int width, int height) {
        int numElements = width * height + 1;
        Point[] points = new Point[numElements];
    
        int x = 0;
        int y = 0;
        int dx = 1;
        int dy = 0;
        int xLimit = width - 0;
        int yLimit = height - 1;
        int counter = 0;
    
        int currentLength = 1;
        while (counter < numElements) {
            points[counter] = new Point(x, y);
    
            x += dx;
            y += dy;
    
            currentLength++;
            if (dx > 0) {
                if (currentLength >= xLimit) {
                    dx = 0;
                    dy = 1;
                    xLimit--;
                    currentLength = 0;
                }
            } else if (dy > 0) {
                if (currentLength >= yLimit) {
                    dx = -1;
                    dy = 0;
                    yLimit--;
                    currentLength = 0;
                }
            } else if (dx < 0) {
                if (currentLength >= xLimit) {
                    dx = 0;
                    dy = -1;
                    xLimit--;
                    currentLength = 0;
                }
            } else if (dy < 0) {
                if (currentLength >= yLimit) {
                    dx = 1;
                    dy = 0;
                    yLimit--;
                    currentLength = 0;
                }
            }
    
            counter++;
        }
    
        Array.Reverse(points);
        return points;
    }
    
    0 讨论(0)
  • 2020-11-22 15:38

    Here's a JavaScript (ES6) iterative solution to this problem:

    let spiralMatrix = (x, y, step, count) => {
        let distance = 0;
        let range = 1;
        let direction = 'up';
    
        for ( let i = 0; i < count; i++ ) {
            console.log('x: '+x+', y: '+y);
            distance++;
            switch ( direction ) {
                case 'up':
                    y += step;
                    if ( distance >= range ) {
                        direction = 'right';
                        distance = 0;
                    }
                    break;
                case 'right':
                    x += step;
                    if ( distance >= range ) {
                        direction = 'bottom';
                        distance = 0;
                        range += 1;
                    }
                    break;
                case 'bottom':
                    y -= step;
                    if ( distance >= range ) {
                        direction = 'left';
                        distance = 0;
                    }
                    break;
                case 'left':
                    x -= step;
                    if ( distance >= range ) {
                        direction = 'up';
                        distance = 0;
                        range += 1;
                    }
                    break;
                default:
                    break;
            }
        }
    }
    

    Here's how to use it:

    spiralMatrix(0, 0, 1, 100);

    This will create an outward spiral, starting at coordinates (x = 0, y = 0) with step of 1 and a total number of items equals to 100. The implementation always starts the movement in the following order - up, right, bottom, left.

    Please, note that this implementation creates square matrices.

    0 讨论(0)
提交回复
热议问题