How to safely transpose Ruby arrays

Ruby arrays have this handy method called transpose which takes an existing 2-dimensional array (i.e. a matrix) and flips it on its side:

>> a = [[1,2], [3,4], [5,6]]
>> puts a.transpose.inspect
[[1, 3, 5], [2, 4, 6]]

Each row becomes a column, essentially. This is fine and dandy for polite arrays. If one of the rows in the original array is not as long as the others, though, Ruby chunders thusly:

>> a = [[1,2], [3,4], [5]]
>> a.transpose 
IndexError: element size differs (1 should be 2)
	from (irb):3:in `transpose'
	from (irb):3

That ain’t pretty, especially if the intent behind using transpose is to render data in a nice columnar fashion. For example, what if we wanted to render a list of high school courses in columns, one column per semester? Grouping the courses by semester and then transposing would do the trick, but only if there were exactly the same number of courses taken each semester. If even one semester differs, Ruby will blow up. What we really want is for Ruby to just ignore the fact that each grouping may have differently sized arrays and transpose anyway, filling in the empty spaces with nils.

Here’s how to do just that:

class Array
  def safe_transpose
    result = []
    max_size = self.max { |a,b| a.size <=> b.size }.size
    max_size.times do |i|
      result[i] = Array.new(self.first.size)
      self.each_with_index { |r,j| result[i][j] = r[i] }
    end
    result
  end
end

Now we call safe_transpose on our matrix of courses and Ruby does the right thing. It calculates the length of the longest row and uses that as the baseline to perform the transposition. So our original example becomes:

>> a = [[1,2], [3,4], [5]]
>> puts a.transpose.inspect
[[1, 3, 5], [2, 4, nil]]

Nice and neat. Caveats: the code above hasn’t been refactored or tested. Your mileage may vary. If you see a better way to do this, let me know and I’ll post an update.

Comments

5 responses to “How to safely transpose Ruby arrays”

  1. […] Pelargir – Musings on software and life from Matthew Bass. » How to safely transpose Ruby arrays […]

  2. Nathan Broadbent (@nathanf77) Avatar

    Hey, here’s my idea for a solution:

    class Array
    def safe_transpose
    max_size = self.map(&:size).max
    self.dup.map{|r| r << nil while r.size < max_size; r}.transpose
    end
    end

  3. Phil Rosenstein Avatar

    This seems like a reasonable addition to the Array class in ruby?

  4. Matthew Bass Avatar

    Thanks for the solution, Nathan. I like it.

    Phil, I agree that it would be a useful addition to Ruby. If not on the Array class itself, at least in a core library somewhere.