3
\$\begingroup\$

I'm working in swift and I've got a custom class Array2D to handle fixed-width, two-dimensional arrays of integers. What I'm trying to do is write a simple, ideally-elegant function max() -> Int? to behave the same way as for a 1-D array: return nil if the array is empty, or else the maximum value in the array. I make no promises about the array contents being positive.

For the sake of simplicity, I'm writing these functions as freestanding rather than parts of a class here.

Broadly speaking, I'm trying to do this:

func max(array: [[Int]]) -> Int? {
  set up a variable for maximum value maxSoFar
  for row in array {
    if row.max() > maxSoFar { maxSoFar = row.max() }
  }
  return maxSoFar
}

What I'm struggling with is avoiding lots of nested optional handling: I have to handle the Int? returned by max for each row of the array, and it looks like I'll need maxSoFar to be an Int? as well, since I don't want to initialize it to zero since all the entries in the array could be negative.

So, a few options:

func max1(array: [[Int]]) -> Int? {
  if array.count == 0 || array[0].count == 0 { return nil }
  var maxval: Int = array[0][0]
  for row in array {
    if let rowmax = row.max() {
      if rowmax > maxval { maxval = rowmax }
    }
  }
  return maxval
}
func max2(array: [[Int]]) -> Int? {
   var maxval: Int?
   for row in array {
     guard let rowmax = row.max() else {
       return nil
     }
     guard let temp = maxval else {
       maxval = rowmax
       continue
     }
     if rowmax > temp {
        maxval = rowmax
     }
    }
    return maxval

Thoughts? Alternatives? I'd be delighted to learn a better way to handle this.

\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! \$\endgroup\$ – Martin R May 8 at 19:26
  • \$\begingroup\$ Thanks! Pleasure to be here. \$\endgroup\$ – Tasha May 8 at 22:44
1
\$\begingroup\$

Your first method does not work correctly if the first row is empty:

print(max1(array: [[], [1, 2], [-3]])) // nil

This is difficult to salvage while avoiding an optional for the current maxval.

Your second method returns nil if any row is empty:

print(max2(array: [[1, 2], [], [3, 4]])) // nil

guard is a useful language feature to avoid the “optional pyramid of doom,” or to “early exit” in exceptional cases. But in your max2() function it makes the program flow difficult to understand (and introduced an error, as we saw above). With plain old if-let-else it would look like this:

func max2(array: [[Int]]) -> Int? {
    var maxval: Int?
    for row in array {
        if let rowmax = row.max() {
            if let temp = maxval {
                if rowmax > temp { maxval = rowmax }
            } else {
                maxval = rowmax
            }
        }
    }
    return maxval
}

But actually the (explicit) handling of all the special cases of an empty array or empty rows can be avoided completely!

The Sequence.joined() can be applied to the nested array, it returns a (lazily evaluated) sequence of all concatenated rows.

This simplifies the implementation to

func max2D(array: [[Int]]) -> Int? {
    return array.joined().max()
}

Or more generic, for nested arrays of comparable elements:

func max2D<T>(array: [[T]]) -> T? where T: Comparable {
    return array.joined().max()
}

You may also consider if it is worth defining a function for that purpose, if you also can call

let myMax = my2DArray.joined.max()

directly.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you so much! I guess I didn't make it clear that elsewhere in this class I enforce the fixed-width 2D array requirement, thus the "single empty row" errors. Your answers are substantially nicer than my original thoughts, so I really appreciate your help! \$\endgroup\$ – Tasha May 8 at 22:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.