user2834794
user2834794

Reputation: 7

Finding index of matching array elements in ruby

Here is my task:

Input: A list of numbers from the keyboard

Output: The second-smallest number in the list, along with its position in the list, with 1 being the position of the first number.

Here is my code so far:

values = []
print "Enter a number: "
a = gets.chomp.to_i
values.push(a)

print "Enter another number: "
b = gets.chomp.to_i
values.push(b)

print "Enter another number: "
c = gets.chomp.to_i
values.push(c)

print "Enter a final number: "
d = gets.chomp.to_i
values.push(d)

new_values = values.sort

second_smallest = new_values[1]
puts "Second smallest number: #{second_smallest}"

if values.include? second_smallest
print "found matching element"
end

I am able to grab the second smallest element from a sorted copy and then check for that element in the original array. How can i grab the index of the matching element in the original array and print it to the user?

Sorry if it's simple I'm brand new to ruby

Upvotes: 0

Views: 672

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110675

def second_smallest(arr)
  smallest = arr.min
  arr.each_with_index.reject { |n,_| n == smallest }.min_by(&:first)
end

second_smallest [3, 1, 4, 1, 2, 3, 5] #=> [2, 4]
second_smallest [1, 1, 1]             #=> nil
second_smallest []                    #=> nil

In the first example the second-smallest number is clearly 2.

Upvotes: 1

Simple Lime
Simple Lime

Reputation: 11035

Ruby has a couple of handy methods on Enumerable and Enumerator, specifically Enumerator#with_index and Enumerable#min_by. So you could do something like:

_, (value, position) = values.each.with_index(1).min_by(2) { |value, _| value }
puts "Second smallest number is #{value} found at position #{position}"

The each method returns an Enumerator if you don't pass it a block, which allows you to chain with_index, passing it's optional argument offset with 1, so the first element is index 1 instead of index 0.

Now, the Enumerator is going to operate on a collection of [value's element, index] and we call min_by on that, telling it we want the 2 smallest values, and splitting the args out in the block parameters to value and ruby's "unused" variable _. So why did we call with_index if we ignore the index? Well, now min_by returns that [value's element, index] that has the 2 smallest value's element and we shunt the smallest back into the "unused" variable _ and let ruby turn that next array, the second smallest, into 2 variables value and position which respectively contain the smallest element and the index (I tend to use position to mean something is 1 based and index to mean it's 0 based, but that's just a personal quirk). Then we can display those back out to the end user.

Do note however, you do not want to sort the values array prior to calling this. If you do you will always see the second smallest element is in position 2. (So in your example code, you want to work on values not new_values, and new_values goes away)


You can play around more with min_by and its return value if you want other variations, for instance, if you want only the third smallest value you could do:

*_, (value, position) = values.each.with_index(1).min_by(3) { |value, _| value }

The same thing, except for the splat operator * at the start, putting all but the last element into that "unused" variable. If you want the second and third smallest, you could do:

*_, (second_smallest_value, second_smallest_position), (third_smallest_value, third_smallest_position) = values.each.with_index(1).min_by(3) { |value, _| value }

to deconstruct and store in variables the last 2 return values of min_by. Or just

*_, second_smallest, third_smallest = values.each.with_index(1).min_by(3) { |value, _| value }

to store the arrays without deconstructing them into individual variables (since it's starting to become a mouthful)

Upvotes: 0

Related Questions