Ruby: How to find the index of the minimum array element?
Asked Answered
F

4

27

Is there any way to rewrite this more elegant? I think, that it's a bad piece of code and should be refactored.

>> a = [2, 4, 10, 1, 13]
=> [2, 4, 10, 1, 13]
>> index_of_minimal_value_in_array = a.index(a.min)
=> 3
Furlong answered 11/2, 2011 at 0:12 Comment(6)
I am not sure about this. Maybe it's my excessive anxiety.Furlong
I would say that this is pretty much as clean as it gets, so no need for "refactoring" here.Munniks
@prostosuper: I wouldn't delete it. It's pretty much a self-answered question, but nevertheless, people trying to get the minimum element index in an array my find this question helpful. So just create an answer that reads something like "Aparrently there is no better solution then a.index(a.min)" and accept it :)Munniks
What if there's more than one minimal value in the array? Do you want the first, the last, or all of them? BTW, I think this is a worthwhile question.Ballyhoo
Wow! You discovered new approach. I haven't thought about it! I read about what you mentioned and completely forgot this nuance. But, actually, I need to find only first minimal element of array. It would be interesting to read about other situations (finding all and only last minimal element).Furlong
@prostosuper: Can you mention why you're looking for the index in the first place? If you can describe the broader problem, maybe there's a different approach required.Ballyhoo
L
8

It would be interesting to read about other situations (finding all and only last minimal element).

ary = [1, 2, 1]

# find all matching elements' indexes
ary.each.with_index.find_all{ |a,i| a == ary.min }.map{ |a,b| b } # => [0, 2]
ary.each.with_index.map{ |a, i| (a == ary.min) ? i : nil }.compact # => [0, 2]

# find last matching element's index
ary.rindex(ary.min) # => 2
Laurenalaurence answered 11/2, 2011 at 4:31 Comment(3)
Is there any appreciable difference between: ary.each.with_index… and ary.each_with_index…? I found, that each_with_index doesn't documented. But ary.methods.grep(/each_with_index/); is true.Furlong
They're both documented in Enumerator, which Array inherits from. each.with_index takes the array and adds an index to it making a array of arrays, with the inner arrays containing the original elements plus indexes. Then you can pass that to other transformers like map. each_with_index wants to iterate over the arrays of arrays. It's a subtle difference but I didn't want an each loop, I wanted to transform.Laurenalaurence
The question wasn't about speed, it was about alternate ways to accomplish something. Why was this selected? I dunno.Laurenalaurence
E
53

I believe this will traverse the array only once and is still easy to read:

numbers = [20, 30, 40, 50, 10]           # => [20, 30, 40, 50, 10]
elem, idx = numbers.each_with_index.min  # => [10, 4]
Ebner answered 4/11, 2012 at 15:40 Comment(2)
Neat solution. Unfortunately Array#last makes it ugly (ary.each_with_index.min.last).Furlong
Maybe ary.each_with_index.min.second is prettier.Phlegm
W
9

This traverses the array only once whereas ary.index(ary.min) would traverse it twice:

ary.each_with_index.inject(0){ |minidx, (v,i)| v < a[minidx] ? i : minidx }
Whoremaster answered 22/11, 2011 at 15:4 Comment(1)
ary.each_with_index.inject([Float::INFINITY,0]) { |(mv,mi), (v,i)| v<mv ? [v,i] : [mv,mi] }Hexahedron
L
8

It would be interesting to read about other situations (finding all and only last minimal element).

ary = [1, 2, 1]

# find all matching elements' indexes
ary.each.with_index.find_all{ |a,i| a == ary.min }.map{ |a,b| b } # => [0, 2]
ary.each.with_index.map{ |a, i| (a == ary.min) ? i : nil }.compact # => [0, 2]

# find last matching element's index
ary.rindex(ary.min) # => 2
Laurenalaurence answered 11/2, 2011 at 4:31 Comment(3)
Is there any appreciable difference between: ary.each.with_index… and ary.each_with_index…? I found, that each_with_index doesn't documented. But ary.methods.grep(/each_with_index/); is true.Furlong
They're both documented in Enumerator, which Array inherits from. each.with_index takes the array and adds an index to it making a array of arrays, with the inner arrays containing the original elements plus indexes. Then you can pass that to other transformers like map. each_with_index wants to iterate over the arrays of arrays. It's a subtle difference but I didn't want an each loop, I wanted to transform.Laurenalaurence
The question wasn't about speed, it was about alternate ways to accomplish something. Why was this selected? I dunno.Laurenalaurence
R
4

I actually like @andersonvom 's answer, it only need to loop the array once and still get the index.

And in case you don't want to use ary.each_with_index.min, here is what you can do:

ary = [2,3,4,5,1]                                             # => [2,3,4,5,1]
_, index_of_minimal_value_in_array = ary.each_with_index.min  # => [1, 4]
index_of_minimal_value_in_array                               # => 4
Raines answered 22/4, 2015 at 7:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.