Looping in a spiral

前端 未结 30 2681
独厮守ぢ
独厮守ぢ 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

    let x = 0
    let y = 0
    let d = 1
    let m = 1
    
    while true
      while 2 * x * d < m
        print(x, y)
        x = x + d
      while 2 * y * d < m
        print(x, y)
        y = y + d
      d = -1 * d
      m = m + 1
    

    There have been many proposed solutions for this problem wrote in various programming languages however they all seem to stem from the same convoluted approach. I'm going to consider the more general problem of computing a spiral which can be expressed concisely using induction.

    Base case: Start at (0, 0), move forward 1 square, turn left, move forward 1 square, turn left. Inductive step: Move forward n+1 squares, turn left, move forward n+1 squares, turn left.

    The mathematical elegance of expressing this problem strongly suggests there should be a simple algorithm to compute the solution. Keeping abstraction in mind, I've chosen not to implement the algorithm in a specific programming language but rather as pseudo-code.

    First I'll consider an algorithm to compute just 2 iterations of the spiral using 4 pairs of while loops. The structure of each pair is similar, yet distinct in its own right. This may seem crazy at first (some loops only get executed once) but step by step I'll make transformations until we arrive at 4 pairs of loops that are identical and hence can be replaced with a single pair placed inside of another loop. This will provide us with a general solution of computing n iterations without using any conditionals.

    let x = 0
    let y = 0
    
    //RIGHT, UP
    while x < 1
      print(x, y)
      x = x + 1
    while y < 1
      print(x, y)
      y = y + 1
    
    //LEFT, LEFT, DOWN, DOWN
    while x > -1
      print(x, y)
      x = x - 1
    while y > -1
      print(x, y)
      y = y - 1
    
    //RIGHT, RIGHT, RIGHT, UP, UP, UP
    while x < 2
      print(x, y)
      x = x + 1
    while y < 2
      print(x, y)
      y = y + 1
    
    //LEFT, LEFT, LEFT, LEFT, DOWN, DOWN, DOWN, DOWN
    while x > -2
      print(x, y)
      x = x - 1
    while y > -2
      print(x, y)
      y = y - 1
    

    The first transformation we will make is the introduction of a new variable d, for direction, that holds either the value +1 or -1. The direction switches after each pair of loops. Since we know the value of d at all points, we can multiply each side of each inequality by it, adjust the direction of the inequality accordingly and simplify any multiplications of d by a constant to another constant. This leaves us with the following.

    let x = 0
    let y = 0
    let d = 1
    
    //RIGHT, UP
    while x * d < 1
      print(x, y)
      x = x + d
    while y * d < 1
      print(x, y)
      y = y + d
    d = -1 * d
    
    //LEFT, LEFT, DOWN, DOWN
    while x * d < 1
      print(x, y)
      x = x + d
    while y * d < 1
      print(x, y)
      y = y + d
    d = -1 * d
    
    //RIGHT, RIGHT, RIGHT, UP, UP, UP
    while x * d < 2
      print(x, y)
      x = x + d
    while y * d < 2
      print(x, y)
      y = y + d
    d = -1 * d
    
    //LEFT, LEFT, LEFT, LEFT, DOWN, DOWN, DOWN, DOWN
    while x * d < 2
      print(x, y)
      x = x + d
    while y * d < 2
      print(x, y)
      y = y + d
    

    Now we note that both x * d and the RHS are integers so we can subtract any real value between 0 and 1 from the RHS without affecting the result of the inequality. We choose to subtract 0.5 from the inequalities of every other pair of while loops in order to establish more of a pattern.

    let x = 0
    let y = 0
    let d = 1
    
    //RIGHT, UP
    while x * d < 0.5
      print(x, y)
      x = x + d
    while y * d < 0.5
      print(x, y)
      y = y + d
    d = -1 * d
    
    //LEFT, LEFT, DOWN, DOWN
    while x * d < 1
      print(x, y)
      x = x + d
    while y * d < 1
      print(x, y)
      y = y + d
    d = -1 * d
    
    //RIGHT, RIGHT, RIGHT, UP, UP, UP
    while x * d < 1.5
      print(x, y)
      x = x + d
    while y * d < 1.5
      print(x, y)
      y = y + d
    d = -1 * d
    
    //LEFT, LEFT, LEFT, LEFT, DOWN, DOWN, DOWN, DOWN
    while x * d < 2
      print(x, y)
      x = x + d
    while y * d < 2
      print(x, y)
      y = y + d
    

    We can now introduce another variable m for the number of steps we take at each pair of while loops.

    let x = 0
    let y = 0
    let d = 1
    let m = 0.5
    
    //RIGHT, UP
    while x * d < m
      print(x, y)
      x = x + d
    while y * d < m
      print(x, y)
      y = y + d
    d = -1 * d
    m = m + 0.5
    
    //LEFT, LEFT, DOWN, DOWN
    while x * d < m
      print(x, y)
      x = x + d
    while y * d < m
      print(x, y)
      y = y + d
    d = -1 * d
    m = m + 0.5
    
    //RIGHT, RIGHT, RIGHT, UP, UP, UP
    while x * d < m
      print(x, y)
      x = x + d
    while y * d < m
      print(x, y)
      y = y + d
    d = -1 * d
    m = m + 0.5
    
    //LEFT, LEFT, LEFT, LEFT, DOWN, DOWN, DOWN, DOWN
    while x * d < m
      print(x, y)
      x = x + d
    while y * d < m
      print(x, y)
      y = y + d
    

    Finally, we see that the structure of each pair of while loops is identical and can be reduced to a single loop placed inside of another loop. Also, to avoid using real valued numbers I've multiplied the initial value of m; the value m is incremented by; and both sides of each inequality by 2.

    This leads to the solution shown at the beginning of this answer.

提交回复
热议问题