Olivia
Olivia

Reputation: 195

How to use Ruby array in this case?

Here is a ruby array:

array = ['x', 3, 0, 4, 4, 7]

I want to map through array, take every integer in the array (except for 0 ) minus 2, and return a new array.

When there are letters, no change will be made to the letters.

Example output:

['x', 1, 0, 2, 2, 5]

This is what I have, but I got the error message saying "undefined method integer? "can someone tell me what is the problem?

    def minusNumber(array)
     array.map do |e|
      if e.integer? && e !== 0
      e - 2
      end
     end
    end

Upvotes: 0

Views: 538

Answers (6)

ForeverZer0
ForeverZer0

Reputation: 2496

Short and sweet.

array.map { |v| v.is_a?(Integer) && !v.zero? ? v - 2 : v }

This eliminates all the needless case statements, multiple iteration, and transformations. Simply takes each element, if it is an integer and greater than 0, then subtracts 2, otherwise does nothing.

Upvotes: 0

ricsdeol
ricsdeol

Reputation: 159

I think this is legible and elegant:

array.map { |v| v.is_a?(Integer) && v == 0 ? v : v -2 }

Upvotes: -1

user3574603
user3574603

Reputation: 3618

You can do this without checking for type.

use of kind_of? is a code smell that says your code is procedural, not object oriented… https://www.sandimetz.com/blog/2009/06/12/ruby-case-statements-and-kind-of

def someMethod(arr)
  arr.map do |item|
    next item unless item.to_i.nonzero?
    item - 2
  end
end

someMethod(["x", 3, 0, 4, 4, 7])

> ["x", 1, 0, 2, 2, 5]

Upvotes: 0

Nossidge
Nossidge

Reputation: 961

The other answers here will work fine with your current input. Something like:

def minusNumber(array)
  array.map do |e|
    if e.is_a?(Integer) && e != 0
      e - 2
    else
      e
    end
  end
end

But here is a more flexible solution. This might be a bit too advanced for you where you are now, but all learning is good learning :-)

Ruby is a language that allows polymorphism in its variables. You can see, using the example input for your method, that the variable e may contain a String object or an Integer object. But it actually may contain any type of object, and Ruby would not care one bit, unless it encounters an error in using the variable.

So. In your example, you need to keep Integers in the output. But what if in the future you need to pass in an array containing some Score objects, and you'd need those in your output too? This is a brand new class that you haven't even written yet, but you know you will later on down the line. There's a way you can re-write your method that will anticipate this future class, and all other Integer-type classes you may someday write.

Instead of using #is_a? to check the type of the object, use #respond_to? to check what methods it implements.

Any class that can be used as an integer should implement the #to_int method. Integer certainly does, and your future Score class will, but String does not. And neither does any other class that can't be considered like an integer. So this will work for all types of values, correctly separating those that respond to #to_int and those that don't.

def minusNumber(array)
  array.map do |e|
    if e.respond_to?(:to_int) && e != 0
      e - 2
    else
      e
    end
  end
end

Again, this stuff might be a bit advanced, but it's good to get in the habit early of thinking of variables in terms of its methods as opposed to its type. This way of thinking will really help you out later on.

Upvotes: 2

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230296

Here's another take on this:

array = ['x', 3, 0, 4, 4, 7]

transformed = array.map do |e|
  case e
  when 0, String
    e
  when Integer
    e - 2
  else
    fail 'unexpected input'
  end
end

transformed # => ["x", 1, 0, 2, 2, 5]

It's a pity that you have to keep the elements from which you didn't subtract 2. I really wanted to do something like this

array.grep(Integer).reject(&:zero?).map{|i| i - 2 } # => [1, 2, 2, 5]

Couldn't find a way yet (which preserves the unprocessed items).

Upvotes: 2

John Baker
John Baker

Reputation: 2398

As mentioned in the comment above you should use is_a? method to check the datatype of an element. The code below should work as expected:

 def minusNumber(array)
   array.map do |e|
     if e.is_a?(String) || e == 0
       e
     else
       e - 2
     end
   end
 end

Instead of the if statement you could also do:

(e.is_a?(String) || e == 0 ) ? e : e-2

Please note that the original object is unchanged unless you use map!

Upvotes: -1

Related Questions