简体   繁体   中英

Iterate over digits of integers inside of array

I have this code below:

 a = [435,276,434]

 def product(a)
   final = 1
   for e in a
     for p in a[e]
       final*=p
     end
   end
   final
 end

 puts product(a)

I'm wondering how I can iterate over this array twice where the result is 4*3*5 = 60, 2*7*6 = 85, and 4*3*4 = 48

I wrote some code up above which I thought would do the trick here but Ruby keeps on returning an error.

A few points to consider:

In Ruby you basically never use a for loop to iterate over things. #each is better. You get to pass a block to that, which gives you all kinds of room for flexibility and expressiveness.

Also, you can't - normally - iterate over an Integer . Remember that an Integer is a store of numerical value, not a particular representation, so it would have to be dependent on the base of the representation you wanted. If you want a string of characters, each of which happen to be numbers, well, guess what? You want a String , like seph's solution here. Or an Array , which would probably make even more sense because each integer would remain an integer and wouldn't have to be parsed back and forth.

Tell you what, let's build a really cool method to do this and staple this on to Integer, and hopefully demonstrate some of Ruby's cool features.

class Integer
  include Enumerable
  def each(base = 10, &blk)
    front, back = abs.divmod(base)
    if front > 0
      front.each(base, &blk)
    end
    yield back
  end
end

This little number takes a base and a block, gets the absolute value of the integer (since technically the minus isn't a digit), then uses divmod to split the number, chopping off the final digit. We store the pieces in front and back. We check to see if there are any more digits, indicated by front being 0, and if there is we recursively call this method, with that block. Then we just yield the back, sending the digit to the block.

Since we have now defined an each method, we are now free to include Enumerable which gets us a ton of stuff!

As long as that modification is active, your product method becomes:

(if you wanted to print 60 84 48): a.map {|n| n.reduce(:*)} a.map {|n| n.reduce(:*)}

(or if you wanted to print 241920): a.reduce(:*).reduce(:*)

Pretty nice!

So, this total solution is quite a bit longer than seph's one-liner, and in truth if I needed to actually do something I would just to_s . Is my solution quicker to execute? Who knows? It's certainly more expressive, though, and that's why you're using Ruby in the first place.

If you want to solve a problem, yeah, absolutely, to_s . But if you want your code to express a philosophy you have about numbers, about how really they're just collections too - and they are, in a weird set theory kind of way, Ruby lets you empower them to be that. And this way that doesn't need Strings at all, they're totally free of their grudging assistance. And you can iterate through different bases, which is super useful if you're doing hex or binary, which preserves more of the numbery essence of them.

In this world that you and I have built together, Jamaal, little integers run wild through the forests with the big boys. And that's wonderful.

You could convert it to a string(.to_s). Then it's easy to get each digit as a char(.chars), convert them back to an integers(.map(&:to_i)) and multiply them together(.reduce(:*))

a = [435,276,434]
a.map {|n| n.to_s.chars.map(&:to_i).reduce(:*) }
=> [60, 84, 48] 

Here's one way you could fix your code:

a = [435,276,434]

def product(a)
  result = [] # Create an empty array that will become [60, 85, 48]
  for e in a
    final = 1
    # Convert the integer e to a string (e.g., "435")
    str = e.to_s
    # Iterate over each char of the string (e.g., "4", "3" and "5")
    str.each_char do |c|
      # Convert the character 'c' to an integer (digit) then multiply final by that integer          
      final *= c.to_i
    end    
    # Append the value of final to the result array
    result << final # e.g., when result = [60], result << 85 => [60, 85]
  end
  result # => [60, 85, 48]
end

product(a) # => [60, 85, 48]

Now let's see how we can improve it. Firstly, we can chain operations and avoid the use of the temporary variable str . Also, you'll find that for loops, each is generally preferable to for (especially because you can use a block with each ), so I'll change that too. While I'm at it, since the each_char loop contains only one statement, I'll write the block with brackets rather than do/end . We now have:

def product(a)
  result = [] # Create an empty array that will become [60, 85, 48]
  a.each do |e|
    final = 1
    e.to_s.each_char {|c| final *= c.to_i}
    result << final
  end
  result
end

When I look at this, I'm thinking I want to convert each element of the array a to something else (the product of its digits). That suggests the use of the Array method map! (or its synonym, collect! ), rather than each . Since a is the argument of the method product , if I use a.map! , that will change the values of a in the method that calls product . That may or may not be OK, but since I'm returning an array of the computed values, it's probably not OK, so I'll apply map! to a copy of a . (It's called a "shallow" copy, which is not important here, but can be in other situations.) We now have this:

def product(a)
  result = a.dup
  result.map! do |e|
    final = 1
    e.to_s.each_char {|c| final *= c.to_i}
    final
  end
  result
end

We don't need the last result , because map! returns result (as well as changing result ). Hey, that also means we can use just map (makes no difference). Also, we can chain a.dup and map to get rid of result :

def product(a)
  a.dup.map do |e|
    final = 1
    e.to_s.each_char {|c| final *= c.to_i}
    final
  end
end

Man, we're cookin' with gas! Next, whenever you see a block that computes the product or sum of something, think inject (or its synomym, reduce ):

def product(a)
  a.dup.map do |e|
    e.to_s.each_char.inject(1) {|final, c| final * c.to_i}
  end
end

Suppose a < 0. What to do? (@Leon's answer twigged me to that possibility.) A negative receiver makes no sense to each , so let's raise an exception if that happens:

def product(a)
  raise RuntimeError, "Receiver must be non-negative" if self < 0
  a.dup.map do |e|
    e.to_s.each_char.inject(1) {|final, c| final * c.to_i}
  end
end

You may want to stop here, but you could replace map with another `inject:

def product(a)
  raise RuntimeError, "Receiver must be non-negative" if self < 0
  a.inject([]) {|result, e| result.concat.e.to_s.each_char.inject(1) {|final, c| final * c.to_i}}
end

Here the argument for inject is an empty array called result . Notice that, since we haven't changed a , we no longer needed dup . (I see @Kingston arrived at a similar answer.) If you prefer, you could write this as:

def product(a)
  raise RuntimeError, "Receiver must be non-negative" if self < 0
  a.inject([]) {|result, e| result << e.to_s.each_char.inject(1) {|final, c| final * c.to_i}; result}
end

but notice the need for that pesky ; result ; result an the end.

You might think that the end result of all these "improvements" is too much of a mind-bender, or that the author is just showing off. That's what I thought when I was new to Ruby. With experience, however, you will find it is very natural and reads like a sentence. It also makes debugging easier: working left-to-right, you can test each link of the chain to make sure it's working.

a.collect{|x| 
  result = 1
  x.to_s.split('').each{|y| result *= y.to_i}
  result
}

If the intent is to just iterate over the digits, you can use the string slice methods too.

num = a[0].to_s # "435" 
final = num[0,1].to_i * num[1,1].to_i * num[2,1].to_i #4*3*5

or

final = num[0..1].to_i * num[1..2].to_i * num[2..3].to_i

For the given question, if you know it is an array of 3 digits each, then you can skip the inner loop and the solution could be like this:

a = [435,276,434]

def product(a)
    a.map! do |digits|
        final = 1
        num = digits.to_s
        final = num[0,1].to_i * num[1,1].to_i * num[2,1].to_i
    end
end

puts product(a).inspect

This answer is just for the said question. Since it manipulates the same array, it edits the receiver. For a more detailed and complete solution, check out Cary Swoveland's answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM