Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transposing a hash in ruby with zip

Is there an easier transposition of the following hash? I have a solution that works but the transpose method is hard to read. The use case is a hash of jobs (j1, j2, j3, etc..) and the dates they occur on (d1, d2, d3, etc...). The requirement is to take a hash of events (e1, e2, e3 etc..) that are grouped by date then job and transpose them to events grouped by job then date.

I experimented with the #zip method but that would require injecting nil at j1:d1 and then stripping them back out of the results. I also struggled to find an example usage of #zip with a block argument. I understand that #zip with a block always returns nil but as a result I couldn't understand how you would ever practically use it.

require 'rspec'
require 'pry'

#     |  d1  |  d2  |  d3  |
#     ----------------------
# j1  |      |  e2  |  e3  |
# --------------------------
# j2  |  e4  |  e5  |  e6  |
# --------------------------
# j3  |  e7  |      |  e9  |
# --------------------------

def transpose(h)
  Hash[
    dates(h).map do |d|
      [
        d,
        Hash[ h.keys.map do |j|
          h[j][d] ? [j, h[j][d]] : nil
        end.compact ]
      ]
    end
  ]
end

def dates(h)
  h.values.map(&:keys).reduce(:|).sort
end

describe "transpose" do

  let(:starting) {
    {
      j1: { d2: :e2, d3: :e3 },
      j2: { d1: :e4, d2: :e5, d3: :e6 },
      j3: { d1: :e7, d3: :e9 }
    }
  }

  let(:transposed) {
    {
      d1: { j2: :e4, j3: :e7 },
      d2: { j1: :e2, j2: :e5 },
      d3: { j1: :e3, j2: :e6, j3: :e9 }
    }
  }

  it { expect(dates(starting)).to eq([:d1, :d2, :d3]) }
  it { expect(transpose(starting)).to eq(transposed) }
end
like image 917
Kevin Monk Avatar asked Nov 01 '22 00:11

Kevin Monk


1 Answers

I've rewritten your transpose method a bit, it should be faster and cleaner:

  def transpose(h)
    h.each_with_object({}) do |(outer, data), ret|
      data.each do |inner, event|
        ret[inner] = {} unless ret[inner]
        ret[inner][outer] = event
      end
    end
  end

It doesn't use unnecessary mapping of dates, and works both ways (changes inner and outer keys). Let me know what you think :)

like image 139
Piotr Kruczek Avatar answered Nov 15 '22 07:11

Piotr Kruczek