2D Loops

Dr Andy Evans

[Fullscreen]

2D loops

  • In spatial analysis, it is quite common to want to loop through 2D collections.
  • To loop through two dimensions, nest loops:
    data = [
    [0,1,2],
    [3,4,5]
    ]

    for row in data:
        for item in row:
            print (item)

2D loops

  • However, it is often necessary to know the coordinates in collection space of the item you're referencing.
    data = [
    [0,1,2],
    [3,4,5]
    ]

    for i in range(len(data)):
        for j in range(len(data[i])):
            print (data[i][j])
  • Nesting loops:
    for i in range(len(data)):
        for j in range(len(data[i])):
            print (data[i][j])
  • Variables re-made if a loop runs more than once.
  • The outer loop starts, then the inner loop starts.
  • When the inner loop has run once, it returns to the start of the inner loop, not the outer loop.
  • It keeps doing this until run to completion (j == len(data[i]); i still zero).
  • What do you think happens to j then, and where does the code go next?
  • Nesting loops:
    for i in range(len(data)):
        for j in range(len(data[i])):
            print (data[i][j])
  • Variables re-made if a loop runs more than once.
  • j is destroyed, and the outer loop increments to i = 1.
  • The inner loop runs again, j recreated as j = 0.
  • The inner loop runs to completion.
  • Thus, each time the outer loop runs once, the inner loop runs to completion.
  • This is repeated until the outer loop completes.
  • The code then moves on.

Nested loops

  • Let's look at i and j:
    i    j
    0    0
    0    1
    0    2
    1    0
    1    1
    1    2
  • This is exactly what we need for moving down a row at a time in our array (i) and then running across each row a space at a time (j).

Issues

  • This is surely one of the neatest algorithms ever!
  • However, it is easy to make mistakes which are avoided by directly using the target variable to access items.
  • There are three problems with the below. Can you spot them?
    for i in range(len(data)):
        for i in range(len(data[j])):
            data[j][i] = 10

2D issues

  • The three mistakes are classics that everyone makes, even experienced coders:
    for i in range(len(data)):
        for i in range(len(data[j])):
            data[j][i] = 10
  • len(data[j])) Looping through to the wrong dimension length. This is very common if the lengths are hard-wired in, so avoid that.

2D issues

  • The three mistakes are classics that everyone makes, even experienced coders:
    for i in range(len(data)):
        for i in range(len(data[j])):
            data[j][i] = 10
  • for i Cutting and pasting your outer loop to make your inner loop, and forgetting to change part of the variable use; here, the inner increment should be to j.

2D issues

  • The three mistakes are classics that everyone makes, even experienced coders:
    for i in range(len(data)):
        for i in range(len(data[j])):
            data[j][i] = 10
  • data[j][i] Switching the indices the wrong way round. This should be data[i][j]. With an non-square array, this will result in trying to read off one side of the array and the program will break. Worse, with a square array, your data will silently be transposed.
  • If you get confused, run through your algorithm by hand on paper, using a 2 by 3 non-square array.

Print

  • Print inserts spaces when comma separated.
  • By default ends with a newline. But this can be overridden (best in scripts):
    print ("a", end=",")
    print ("b", end=",")
    print ("c")

    a,b,c
  • When to act
    for i in range(len(data)):
        # Line here done every outer loop
        for j in range(len(data[i])):
            # Line here done every inner loop
            data[i][j] = 10
        # Line here done every outer loop
  • When to act
    for i in range(len(data)):
        for j in range(len(data[i])):
            print (data[i][j], end=",")
        print ("")

Moving window algorithms

  • A prime example of why we might want the coordinates is moving window algorithms.
  • Let's start with a simple allocation to the current item:
    for i in range(len(data)):
        for j in range(len(data[i])):
            data[i][j] = 10
Image: see code
  • Looping through the same positions in two collections:
    for i in range(len(dataA)):
        for j in range(len(dataA[i])):
            dataA[i][j] = dataB[i][j]
Image: see code
  • Looping through two arrays at positions relative to one array (note boundary problem):
    for i in range(len(dataA)):
        for j in range(len(dataA[i])):
            dataA[i][j] = dataB[i-1][j-1]
Image: see code

Boundary problems

  • Various solutions.
  • Depends on problem context
Image: see code Wrap boundaries:
    Suitable for modelling abstract landscapes
Only process as many cells as you can:
    Suitable for modelling non- abstract landscapes
Only process cells that can have complete processing:
    Suitable for image processing

Multiple targets

  • If you know the number of elements in the second dimension of a 2D list or tuple, you can:
    data = [
    [0,1],
    [2,3],
    [4,5]
    ]

    for a,b in data:
        print (str(a) + " " + str(b))

zip uses

  • Really useful for looping through two sets of data at the same time.
  • Here, for example, we use it to make a dictionary from two lists:
    a = [1,2,3,4,5]
    b = [10,20,30,40,50]
    z = zip(a,b)
    d = {}
    for t in z:
        d[t[0]] = t[1]
    print(d)

Builtins: Iterators

  • If you have any doubts about the iterator returning a value, this will return a default value at the end of the iterator.
    next(iterator[, default])
  • Obviously make sure it doesn't create an infinite loop.
  • If the default isn't given, it produces a warning at the end.
    a = list(range(3))
    it = iter(a)
    for i in range(5):
        print(next(it, "missing"))
  • Prints:
    0
    1
    2
    missing
    missing

Reversed(sequence)

  • Get a reverse iterator.
    reversed(seq)

    a = list(range(3))
    ri = reversed(a)
    for i in ri:
        print(i)
  • Prints:
    2
    1
    0

Sorting lists

  • Two types of sort function:
    a.sort()
    # Sorts list a.
    # From then on, the list is sorted.
    b = sorted(a)
    # Copies list a, sorts it, and attaches the copy to b.
  • The former is more memory efficient. The latter leaves the original unchanged and will work on tuples, as it generates a list.
  • There are a wide variety of options for sorting, including defining the kind of sorting you want. See: https://docs.python.org/3/howto/sorting.html