Jwaldon
Jwaldon

Reputation: 45

Ruby Second Smallest Number and index

Trying to use Ruby to find the second smallest number and the index from an input. I have the logic working from a hard coded array but can't seem to get the input from a user to work successfully. Thoughts?

print "Enter a list of numbers: "
nums = [gets]

#nums = [3,1,7,5]
lists = nums.sort
A = lists[1]
B = nums.find_index(A)

print "The second smallest number is: #{A} and the index is #{B}"

Upvotes: 0

Views: 640

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110675

Suppose the user entered

str = " 2, -3 , 4,   1\n"

Then

arr = str.split(/ *, */).map(&:to_i)
  #=> [2, -3, 4, 1]

The regular expression matches zero or more spaces followed by a comma followed by zero or more spaces.

The second smallest element, together with its index, can be obtained as follows.

n, i = arr.each_with_index.min(2).last
  #=> [1, 3]
n #=> 1
i #=> 3

See Enumerable#min.

The steps are as follows.

enum = arr.each_with_index
  #=> #<Enumerator: [2, -3, 4, 1]:each_with_index>  

We can see the elements that will be generated by this enumerator by converting it to an array.

enum.to_a
  #=> [[2, 0], [-3, 1], [4, 2], [1, 3]] 

Continuing,

a = enum.min(2)
  #=> [[-3, 1], [1, 3]] 

min(2) returns the two smallest elements generated by enum. min compares each pair of elements with the method Array#<=>. (See especially the third paragraph of the doc.) For example,

[2, 0] <=> [-3, 1]
  #=> 1
[4, 2] <=> [1, 3]
  #=> 1
[1, 3] <=> [1, 4]
  #=> -1

min would therefore order these pairs as follows.

[2, 0] > [-3, 1]
[4, 2] > [1, 3]
[1, 3] < [1, 4]

I've included the last example (though enum.to_a does not contain a second 1 at index 4) to illustrate that the second element of each two-element array serves as a tie-breaker.

As we want the second-smallest element of arr there is one final step.

n, i = a.last
  #=> [1, 3]
n #=> 1 
i #=> 3 

Note that

n, i = [3, 2, 4, 2].each_with_index.min(2).last
  #=> [2, 3]
n #=> 2

If we wanted n to equal 3 in this case we could write

n, i = [3, 2, 4, 2].uniq.each_with_index.min(2).last
  #=> [3, 0]
n #=> 3

If the entries were floats or a mix of floats and integers we need only replace to_i with to_f.

str = "6.3, -1, 2.4, 3\n"
arr = str.split(/ *, */).map(&:to_f)
  #=> [6.3, -1.0, 2.4, 3.0] 
n, i = arr.each_with_index.min(2).last
  #=> [2.4, 2]
n #=> 2.4
i #=> 2 

Upvotes: 3

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84343

Scan Input Strings and Map to Integers

The Kernel#gets method returns a String, so you have to parse and sanitize the user input to convert it to an Array. For example:

nums = gets.scan(/\d+/).map &:to_i

This uses String#scan to parse the input string, and Array#map to feed each element of the resulting array to String#to_i. The return value of this method chain will be an Array, which is then assigned to your nums variable.

Results of Example Data

Given input with inconsistent spacing or numbers of digits like:

1,2, 3,  4,     5, 10, 201

the method chain will nevertheless assign sensible values to nums. For example, the input above yields:

#=> [1, 2, 3, 4, 5, 10, 201]

Upvotes: 1

Related Questions