Aaron Fine
Aaron Fine

Reputation: 129

Converting Ruby loop iteration with a yield statement to Python

I'm trying to teach myself Python by translating the code in the Mazes for Programmers book from Jamis Buck. It's a great book but it's written in Ruby and I'm getting stuck on understanding some of the Ruby code.

To understand the problem, I've included a reduced version of the codebase

cell.rb

class Cell
  attr_reader : row, :column

def initialize(row, column)
  @row, @column = row, column
end

grid.rb

require 'cell'
class Grid
  attr_reader :rows, :columns

def initialize(rows, columns)
  @rows = rows
  @columns = columns
  @grid = prepare_grid
end

def prepare_grid
  Array.new(rows) do |row|
    Array.new(columns) do |column|
      Cell.new(row, column)
    end
  end
end

So far so good. All of the above is easy to understand and translate to Python. Then there are the following two functions as part of the Grid class.

def each_row
  @grid.each do |row|
    yield row
  end
end

def each_cell
  each_row do |row|
    row.each do |cell|
      yield cell if cell
    end
  end
end

What are the last two functions here actually doing? I've found something similar here that leads me to think that the Python version will need to accept an optional lambda variable, test to see that it's not null, and if it isn't then run the code associated with the variable. The problem is that I know the intent of these functions is to be an iterator and I don't think that adding in a lambda will help.

There is a somewhat similar question already here on StackOverflow which makes me think that the answer is trivial, but I don't know enough about Ruby to be able to intuit the answer or ask Google the right questions.

Upvotes: 2

Views: 177

Answers (1)

Jörg W Mittag
Jörg W Mittag

Reputation: 369498

Then there are the following two functions as part of the Grid class.

These aren't functions. They are methods.

def each_row
  @grid.each do |row|
    yield row
  end
end

def each_cell
  each_row do |row|
    row.each do |cell|
      yield cell if cell
    end
  end
end

What are the last two functions here actually doing?

The each_row method takes a block as parameter and will successively yield all elements of the @grid array. @grid is structured as an array of arrays, representing rows of cells. In other words, each_row will successively yield each row of the grid, i.e. it is an iterator method for rows.

The each_cell method takes a block as parameter and will successively yield all elements of the row arrays in the grid @grid array. In other words, each_cell will successively yield each cell of the grid if it exists, i.e. it is an iterator method for cells.

The literal translation to Python would be something like this (untested):

def each_row(self, f):
  self.grid.each(lambda row: f(row))

def each_cell(self, f):
  self.each_row(lambda row: lambda cell: if cell: f(cell))

But, it just doesn't make sense to translate code from one language to another this way. Using lambdas for iteration in Python is non-idiomatic. Python uses iterators for iteration. So, instead of having each_row and each_cell iterator methods, you would rather have row_iterator and cell_iterator getters which return iterator objects for the rows and cells, so that you can then do something like:

for cell in grid.cell_iterator

instead of

grid.each_cell(lambda cell: …)

Something like this (also untested):

def row_iterator(self):
  for row in self.grid: yield row

def cell_iterator(self):
  for row in self.row_iterator:
    for cell in row:
      if cell: yield cell

When you "translate" code from one language to another, you cannot just translate it line-by-line, statement-by-statement, expression-by-expression, subroutine-by-subroutine, class-by-class, etc. You need to re-design it from the ground up, using the patterns, practices, and idioms of the community and the types, classes, subroutines, modules, etc. from the language and its core and standard libraries.

Otherwise, you could just use a compiler. A compiler is literally defined as "a program that translates a program from one language to another language". If that is all you want to do, use a compiler. But, if you want the translated code to be readable, understandable, maintainable, and idiomatic, it can only be done by a human.

Upvotes: 4

Related Questions