4

I have some array, and I need to find minimal non zero integer in each row. Is there a way of doing it with min(by:)?

for example

var row = [0,0,0,0,0,0,0,0,1,3,5,6,9]

so I need to get 1

by doing row.min() I always get 0.

I was told that I can do it with min{by:} but I don't fully understand the syntax.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
Mod3rnx
  • 828
  • 1
  • 10
  • 21

4 Answers4

8

You can filter the array for desired values, and use Array.min() method, like so

row.filter{ $0 > 0 }.min()

Or following will only work if array has ordered numbers

row.first(where: { $0 > 0 })
AamirR
  • 11,672
  • 4
  • 59
  • 73
  • 1
    Make it lazy to avoid the intermediate array. – Alexander Apr 25 '19 at 14:13
  • Probably, that's what @Alexander is talking about: https://stackoverflow.com/questions/46808889/what-is-the-difference-between-filter-first-and-firstwhere – Ahmad F Apr 25 '19 at 14:16
  • @Alexander but I think it would not be helpful since `min()` has to evaluate all elements... – Ahmad F Apr 25 '19 at 14:33
  • @AhmadF That's fine, it can evaluate the elements of a lazily filtered collection. Nothing is requiring it for have to store those elements in memory, only to almost immediately delete them again. – Alexander Apr 25 '19 at 14:34
  • @Alexander delete from where, memory? – AamirR Apr 25 '19 at 14:35
  • 3
    @AamirR `row.filter({ $0 > 0 })` will produce an array as a result. Memory will be allocated to store `[1,3,5,6,9]`. `min` will then go through the numbers of that array, pick out the smallest (`1`), after which that intermediate array isn't needed, and will be deleted. You're spending `O(row.count)` time to copy these elements, only to use them for a brief moment and delete them right after. Instead, you can just do `row.filter { 0 < $0 }.min()`, and skip the need for that intermediate array entirely. – Alexander Apr 25 '19 at 14:42
  • See [this answer](https://stackoverflow.com/a/51917427/1630618) for a discussion of using `lazy` with an array. – vacawama Apr 25 '19 at 14:43
  • 2
    @Alexander, nice comment except you left out `lazy` in your final example. – vacawama Apr 25 '19 at 14:46
  • 2
    @vacawama Damn it, I did. Should be: Instead, you can just do `row.lazy.filter { 0 < $0 }.min(), and skip the need for that intermediate array entirely. – Alexander Apr 25 '19 at 14:47
  • @AhmadF Why did you delete your answer? It's perfect. – Alexander Apr 25 '19 at 14:47
  • @Alexander downvoted for a reason... Honestly sometimes I feel disappointed from facing this issue. – Ahmad F Apr 25 '19 at 14:57
  • @AhmadF Someone (not me) downvoted your answer before you edited it, since the original version was incorrect. I also briefly commented on the edited version right before you deleted it, but in retrospect that comment (now deleted) was incorrect, and the edited version would probably deserve the accepted answer. – Arkku Apr 25 '19 at 15:00
  • @Arkku (and Alex) if you believe that it's a not bad answer I don't mind to undelete it with a downvote. Thank you guys. – Ahmad F Apr 25 '19 at 15:06
6

Just filter your array to keep all elements greater than 0, and find the min among those.

Use a lazy operation to prevent the intermediate array allocation usually caused by Array.filter(_:). See https://stackoverflow.com/a/51917427/1630618 for more details.

let minNonZeroValue = row.lazy.filter { 0 < $0 }.min()
Alexander
  • 59,041
  • 12
  • 98
  • 151
3

You could do it like this:

let result = row.lazy.filter { $0 > 0 }.min()

Note that result is an optional Int since result might not contain any element that matches the condition.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
2

There are obviously many ways, one way (which doesn't create an intermediate array, or require row to be sorted) is with reduce:

row.reduce(nil as Int?) { minSoFar, this in
    guard this != 0 else { return minSoFar }
    guard let minSoFar = minSoFar else { return this }
    return min(minSoFar, this)
}

Which is equivalent to, but more readable than, the shorter:

row.reduce(nil as Int?) { $1 != 0 ? min($0 ?? $1, $1) : $0 }

The result is optional, since there might not be any non-zero elements.

edit: The min(by:) solution could also indeed be used, but it is also somewhat unreadable and returns 0 in case there are no non-zero elements (instead of nil):

row.min { $0 == 0 ? false : ($1 == 0 ? true : $0 < $1) }
Arkku
  • 41,011
  • 10
  • 62
  • 84
  • Works, but way more complex than needed. Check out my answer – Alexander Apr 25 '19 at 14:49
  • @Alexander I must admit that my answer is mainly based on old habits starring Bruce Willis, and the `lazy` solution is probably the way to go these days. =) – Arkku Apr 25 '19 at 15:04