0

Here is a simple Python program that outputs changes in value of consecutive elements:

amounts = [10, 9, 10, 3, 100, 100, 90, 80, 10, 30, 10]
for i in range(0, len(amounts)-1):
  if amounts[i+1] > amounts[i]:
    print("up", amounts[i+1]-amounts[i])
  elif amounts[i+1] < amounts[i]:
    print("down", amounts[i]-amounts[i+1])
  else:
    print("stay")

The following Ruby code is my first attempt at translating the above from Python:

amounts = [10, 9, 10, 3, 100, 100, 90, 80, 10, 30, 10]
for i in 0..(amounts.count)-1 do
  if amounts.at(i+1) > amounts.at(i)
    printf "up %d", (amounts.at(i+1)-amounts.at(i))
    if amounts.at(i+1) < amounts.at(i)
      printf "down %d", (amounts.at(i)-amounts.at(i+1))
    end
  else
    print "stay"
  end
end

This Ruby code returns NoMethodError as it is. If anyone could enlighten me as to the magical ways of Ruby, I would be very pleased.

1
  • Does this even work in Python ? You’re accessing an out of range index of amounts. Commented Mar 23, 2021 at 13:32

2 Answers 2

5

Here is a "direct" translation of the python code to ruby - note that the syntax is almost identical! (The only subtle differences are elif vs elsif and the print/puts syntax.)

amounts = [10, 9, 10, 3, 100, 100, 90, 80, 10, 30, 10]
for i in 0..(amounts.count)-2 do
  if amounts[i+1] > amounts[i]
    puts "up #{amounts[i+1]-amounts[i]}"
  elsif amounts[i+1] < amounts[i]
    puts "down #{amounts[i]-amounts[i+1]}"
  else
    puts "stay"
  end
end

There was nothing wrong with using Array#at, but it's just an alias for Array#[]. So if your question is about "translating" the code, then I don't see the point in making changes-for-the-sake-of-changes.

Note that I also fixed the off-by-one error that's present in both versions of your code -- you can only run this up to len(amounts)-2, not len(amounts)-1, or you'll go out of bounds!

However, this is not really written in "the ruby way". for loops are rarely used in ruby, because the language has other highly expressive iterators. Here is how I would have written it, using Enumerable#each_cons:

amounts = [10, 9, 10, 3, 100, 100, 90, 80, 10, 30, 10]
amounts.each_cons(2) do |first, second|
  if first < second 
    puts "up #{second - first}"
  elsif first > second
    puts "down #{first - second}"
  else
    puts "stay"
  end
end

# Output:
down 1
up 1
down 7
up 97
stay
down 10
down 10
down 70
up 20
down 20

One advantage to this syntax is that off-by-one errors (like you had in the python!) aren't really possible, because you're just saying "loop over all elements" without having to worry about tracking the indexes yourself.

This is actually a really good example for appreciating the elegance of ruby, if code is written in "the ruby way" rather than merely a direct translation.

Sign up to request clarification or add additional context in comments.

3 Comments

Note that for "simple" iterations in ruby, it would be common to use methods like each or map, rather than a for loop. Like I said, that method is very rarely the "best" way of writing it in ruby.
I understand that for loop is not really the best in Ruby (apparently, you Ruby guys really are a superior breed) but as you said, I am just looking for a simple "direct" translation from Python for now in order to facilitate my learning. I want to become a Ruby expert. I really do have an appreciation for the elegance of Ruby. There are plenty of things a Python pleb like me would not yet know.
Didn't mean to come off as snobbish, sorry! I only wanted to emphasise that translating the code "as directly as possible" might only involve some tiny syntax changes like elif --> elsif, but you don't really gain full benefits from doing that. The converse is also true, of course -- Python has excellent list comprehension syntax, for example, that you could easily miss out on by making a "dumb" direct translation of ruby --> python!
0

You could also employ an enumerator.

amounts = [10, 9, 10, 3, 100, 100, 90, 80, 10, 30, 10]
enum = amounts.each
  #=> #<Enumerator: [10, 9, 10, 3, 100, 100, 90, 80, 10, 30, 10]:each>
loop do
  diff = -enum.next + enum.peek
  puts case diff <=> 0
  when -1 then "down #{-diff}"
  when  0 then "stay"
  else         "up #{diff}"
  end
end
down 1
up 1
down 7
up 97
stay
down 10
down 10
down 70
up 20
down 20

See Array#each (or use Object#to_enum), Kernel#loop, Enumerator#next, Enumerator#peek and Integer#<=> (the last referred to as the spaceship operator).

As explained by the docs, when peek is executed after next has caused enum to generate its last element a StopIteration exception is raised which loop handles by breaking out of the loop.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.