Making sense of 2D arrays

Arrays.

They are the data structure I use most when programming in Swift. Easy to insert, easy to loop through, and sometimes, I can even remember how to successfully map through one and transform values.

let lonesomeDoveCharacters = ["Gus", "Woodrow", "Deets"]
lonesomeDoveCharacters.append("Jake")

Accessing the values in an array is just a matter of using a for-in loop...

for character in lonesomeDoveCharacters {
    print("Character name: \(character)")
}

However, when an extra set of braces is thrown in there to create a two dimensional array, my brain just shuts down.

[
 ["Gus", "Woodrow", "Deets"], 
 ["Jake", "Laurie", "Newt"], 
 ["July", "Roscoe", "Clara"]
]

Lately I've struggled to get my brain going in the mornings. To combat this, I've started working problems on the HackerRank. The first few days were pretty straight forward and didn't require much though to write a working solution that also passed all the test cases. Then, I came across this one:

The basics of the problem are that you have a square matrix and they want you to find the absolute value of the difference in the sum of the diagonals.

1 2 3
4 5 6
9 8 9

The diagonal sums are (1 + 5 + 9) and (3 + 5 + 9). Then the want the absolute value of the difference between those sums or |15 - 17| which would be 2.

My first mistake when thinking about this problem was trying to collect all the information I needed in one pass. My second mistake was not immediately thinking I needed access to the index values of the arrays (both the outside array that holds all the information and each individual array sitting inside of it.)

Admittedly, I got pretty discouraged. I was trying to think this through in my head and anytime I attempt to solve a problem this way, frustration sets in or mistakes happen because I can't "see" the solution.

Additionally, I chose to just focus on one aspect of the problem. How would I access the values of the forward diagonal (1, 5, 9). This helped me with problem #1. Break up the problem into a subsets of problems. Now, I will be the first to admit I didn't know what all the subsets would be, but I knew this was one of them.

The first thing I did was pull out some paper and a pencil and started writing down what was happening during each iteration of the loop.

let twoD = [[1,2,3], [4,5,6], [9,8,9]]

Position 0 in twoD is the array [1, 2, 3]. Let's call this subArray1.

Position 1 in twoD is the array [4, 5, 6] Let's call this subArray2.

Position 2 in twoD is the array [9, 8, 9] Let's call this subArray3.

Interesting. The index of each object in the twoD array is also the index of the value I need inside each object (subArray).

Once I diagrammed that out (and bought a whiteboard off Amazon so I wouldn't have to use paper as often), I realized that an enumerated for-in loop would give me access to the objects (subArrays) as well as the index value of each object (subArray). So I could write something like this:

func diagonalDifference(from arr: [[Int]]) -> Int {
    var frontWardsSum = 0
    for (index, subArray) in arr.enumerated() {
        frontWardsSum += subArray[index]
    }
    return frontWardsSum
}

The values going backward were still not clear and I had to talk out loud.

"I want to go to the end of the first subArray, grab the value and add it to an existing sum. I want to go to the end of the second subArray minus one index value, grab that value and add it to the existing sum. I want to go to the end of the third subArray minus two index values, grab that value and add it to the existing sum."

As you can see, a pattern emerges. The trick is to write code to tell it just that.

Step One: find the end of each subArray object.
Step Two: figure how many places back from the end to travel.
Step Three: subtract places necessary from endIndex
Step Four: grab the value using appropriate index.
Step Three: Add it to an existing sum.

I decided to put this in it's own loop so I can pay attention to just what is happening. I wound up realizing later I could refactor everything into just one loop instead of using two separate ones in my function.

func diagonalDifference(from arr: [[Int]]) -> Int {
    // to obtain frontwards diagonal value
    var frontwardsSum = 0
    for (index, subArray) in arr.enumerated() {
        frontWardsSum += subArray[index]
    }

    // to obtain backwards diagonal value
    var backwardsSum = 0
    for (index, subArray) in arr.enumerated() {
        let endPosition = subArray.count - 1
        backwardsSum += subArray[endPosition - index]
    }

    let absoluteValue = abs(frontwardsSum - backwardsSum)
    return absoluteValue
}

endPosition had to be reduced by 1 on account of arrays being zero-based indexing, so if the array has three objects (as these do), those positions are 0, 1, and 2 rather than 1, 2, and 3.

From there, after drawing it out that the first object I didn't want to subtract anything because I was already at the end position. Fortunately, the index value is 0 because it is the first object & position in the twoD array so endPosition minus 0 is still endPosition. As I work to the next object, it's position is 1 and the object value I need is endPosition minus 1 and so on.

Finally, I could take the absolute value of the two sums by using Swift's built in abs(_:) function.

I ran the test in hacker rank and it passed, but I realized that what was being done in two loops, could all be combined in one, so I cleaned everything up and submitted the following:

func diagonalDifference(from arr: [[Int]]) -> Int {
    var frontwardsSum = 0
    var backwardsSum = 0
    for (index, subArray) in arr.enumerated() {
        // to obtain frontwards diagonal value
        frontWardsSum += subArray[index]

        // to obtain backwards diagonal value
        let endPosition = subArray.count - 1
        backwardsSum += subArray[endPosition - index]
    }
    let absDifference = abs(frontwardsSum - backwardsSum)
    return absDifference
}

Perhaps there are people that can do all of this in their head. Or maybe they work with multi-dimensional arrays often enough, that accessing the necessary info has become second nature. I know for me, though, that pencil and paper, talking out loud, and trying to keep the steps small is what allowed me to work through this challenge. Moreover, I know you hear more seasoned developers say "start with whatever approach makes sense first. You can optimize it later." It really is true. I had three or four pages of scrap paper just doodling what would happen at each iteration--which is why I bought a whiteboard for my office for future challenges.

Lastly, for those of you who struggle with problems like this, you are not alone. I wanted to give up several time due to frustration. However, when I took the keyboard away and started putting pencil to paper, I was able to slowly talk myself through the problem without focusing on the blinking cursor on my screen waiting on my solution.

17