1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
FIELDS = [ 'first', 'last', 'band' ] DATA = [['Kurt', 'Cobain', 'Nirvana'], ['Jimi', 'Page', 'Led Zeppelin'], ['Noel', 'Gallagher', 'Oasis'], ['Thom', 'Yorke', 'Radiohead']] def combine_fields_and_data(fields, data) new_data = [] data.each do |row| row_hash = {} row.each_with_index do |column, i| row_hash[fields[i].to_sym] = column end new_data.push(row_hash) end return new_data end
Refactorings
No refactoring yet !
danielharan
September 22, 2008, September 22, 2008 16:00, permalink
Wrap that in an object lets the BandMember know how to handle the order of params, rather than having that in the loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class BandMember attr_accessor :first, :last, :band def initialize(first, last, band) @first, @last, @band = first, last, band end end DATA.map {|row| BandMember.new(*row) } # Alternately, with no new class: def datum_to_band_member(first, last, band) {:first => first, :last => last, :band => band} end DATA.collect {|e| datum_to_band_member *e }
ruphin.myopenid.com
September 22, 2008, September 22, 2008 16:13, permalink
I think this works. It uses Enumerable.zip to get the Arrays in a right format to just make a new Hash.
1 2 3 4 5 6 7
def combine_fields_and_data(fields, data) new_data = [] data.each do |row| new_data << Hash[fields.zip(row)] end return new_data end
charlesroper
September 22, 2008, September 22, 2008 16:54, permalink
OK, good stuff. Elegant solutions. Many thanks! Now what if I don't know the number of fields or their names in advance? In other words, I've pulled the fields and the data from a database table but I don't know what that table contains. Ruphin's solution covers this scenario whereas Daniel's does not.
danielharan
September 22, 2008, September 22, 2008 17:07, permalink
It's rather odd not to know what's in your database :O
I couldn't get ruphin's code to work. That said, if it's with an ORM like ActiveRecord, there are built-in methods to get attributes in a hash format.
Adam
September 22, 2008, September 22, 2008 17:11, permalink
1 2 3
def combine_fields_and_data(fields, data) data.map { |row| Hash[*fields.zip(row).flatten] } end
Adam
September 22, 2008, September 22, 2008 17:12, permalink
Alternatively, if you like danielharan's first solution, you can load the field names at runtime like this:
1 2
BandMember = Struct.new(*FIELDS.map { |field| field.to_sym }) DATA.map { |row| BandMember.new(*row) }
charlesroper
September 22, 2008, September 22, 2008 17:42, permalink
Brilliant! The refactoring brainpower in here is awesome. I'm constantly amazed at how elegant Ruby can be.
Daniel, it's interesting you couldn't get Ruphin's solution to work as it worked just fine for me. Adam, I've not looked at structs before; I'll have go get the full skinny on them. Thanks for the pointer.
And yes, I agree it is odd not knowing what's in my database, but that's the particular situation I'm faced with. :)
ruphin.myopenid.com
September 22, 2008, September 22, 2008 23:29, permalink
My solution was based on something similar to what Adam posted. I couldn't get that particular code to work, but I used that same method with zip and flatten to end up with what I posted, that's the only version I could get working.
charlesroper
September 23, 2008, September 23, 2008 14:38, permalink
One minor point regarding Adam's solution: the keys aren't converted to symbols. Here's what I've done; is this the best way?
1 2 3 4
def combine_fields_and_data(fields, data) fields.collect! { |f| f.to_sym } data.map { |row| Hash[*fields.zip(row).flatten] } end
First posting here. I just wondered if there were a cleaner way to do the following. I looks a bit ugly to me.
The FIELDS need to be converted to symbols and become the keys of each hash. Each nested array in the DATA array becomes the data in each hash.