user7055375
user7055375

Reputation:

How do I find the minimum element in an array of strings, but only considering integers?

I'm using Ruby 2.4. I have an array of strings that are supposed to be integers ...

["1", "55", "25", ... ]

I have the following for figuring out the minimum number in the array

data.map(&:to_i).min

This works great, except for the times when a non-integer string creeps into my array, like

["1", "a", "2", "3"]

With my data.map(&:to_i).min line, the above returns zero. How do I modify my code so that it will only take the min of elements in the array that are actually integers?

Upvotes: 2

Views: 477

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110675

When I want to convert a string to an integer, if and only it is a string representation of an integer, I always use Kernel::Integer:

arr = ["3", "a", "2", "0x01", "0c", "0b11", "0o03", "4", "1_000"]

arr.select { |s| Integer(s) rescue nil }.min_by { |s| Integer(s) }
  #=> "0x01" 

Here

arr.select { |s| Integer(s) rescue nil }
  #=> ["3", "2", "0x01", "0b11", "0o03", "4", "1_000"] 

Note "0x01".to_i #=> 0 (not!), which is why Integer must be used twice.

If the string is not a representation of an integer, Integer raises an exception:

Integer("a") 
  #=> ArgumentError: invalid value for Integer(): "a"

which is rescued in-line, causing nil to be returned, resulting is the string not being selected. (One could of course go the extra mile and use a clause that only rescues ArgumentError exceptions.)

The question asks for an element of the array (a string) to be returned. If its integer equivalent is desired, you can write:

a = arr.map { |s| Integer(s) rescue nil }.compact.min
  #=> 1

where

arr.map { |s| Integer(s) rescue nil }
  #=> [3, nil, 2, 1, nil, 3, 3, 4, 1_000]

Note

['a', 'b'].map { |s| Integer(s) rescue nil }.compact.min
  #=> nil 

Upvotes: 3

Sagar Pandya
Sagar Pandya

Reputation: 9497

Using Enumerable#grep_v we can do the following and exclude wordy-strings:

 arr = ["1", "a", "2", "3"]
 arr.grep_v(/[a-zA-Z]+/).min_by(&:to_i) #=> "1"

Modify the regexp as you feel appropriate.

Upvotes: 0

tadman
tadman

Reputation: 211560

You have a few ways of doing this, but the easiest is to screen out non-numerical values if you want to ignore the garbage:

data = ["1", "a", "2", "3"]
data.grep(/\A\-?\d+\z/).map(&:to_i).min
# => 1

The grep method makes it pretty easy to pick out things that match a particular pattern, as well as many other things.

Upvotes: 4

Related Questions