Topic: Mutli-Dimension Arrays and Recursive Hashes?

I'm a PHP convert so I am used to having those two structures. Does anyone have any documentation  on how/if these things are implemented in Ruby?

It's great to be able to do things like:

myArray[0] = [1,2,3,4]
myArray[1] = [1,2,3,4]

And access it like:

stuff = myArray[0][3]

And hashes like so:

myHash["key1"] = ["v1" => 1, "v2" => 2, "v3" => 3,"v4" => 4]
myHash["key2"] = ["v1" => 1, "v2" => 2, "v3" => 3,"v4" => 4]
value3 = myHash["key2"]["v3"]

I don't think any of the above is valid PHP (I have Ruby on the brain wink), but you get the idea I hope. Is there a way to do things like that in Ruby (and consequently in Rails?)

Any information would be helpful. Thanks.

Re: Mutli-Dimension Arrays and Recursive Hashes?

Yeah, you can basically.  Check out the commandline interactive ruby "irb", it is really very cool!  Here's a sample that shows me playing with your questions:

$ irb
irb(main):001:0> myArray = [[1,2,3,4],[1,2,3,4]]
=> [[1, 2, 3, 4], [1, 2, 3, 4]]
irb(main):002:0> stuff = myArray[0][3]
=> 4
irb(main):003:0> puts stuff
4
irb(main):004:0> myHash = { "first" => {"a" => 1, "b" => 2, "c" => 3}, "second" => {"z" =>  4, "y" => 5, "x" => 6}}
=> {"second"=>{"x"=>6, "y"=>5, "z"=>4}, "first"=>{"a"=>1, "b"=>2, "c"=>3}}
irb(main):005:0> puts myHash["first"]["a"]
1
=> nil
irb(main):006:0> puts myHash["second"]["y"]
5
=> nil

Re: Mutli-Dimension Arrays and Recursive Hashes?

That is really cool! Thanks for showing me that. Something that is really throwing me for a loop is that I am just testing out these arrays/hashes and I do something like this:

@hash_events["blah"]["innerblah"] = "stuff"

In a view but it is complaining:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occured while evaluating nil.[]

Did it expect something from the controller? Not sure... I assume anything that works in the IRB will work in Rails as well?

Re: Mutli-Dimension Arrays and Recursive Hashes?

It's just a way a hash behaves in Ruby. Since @hash_events["blah"] has not been set, it returns nil. Calling ["innerblah"] after this is like calling nil["innerblah"] which is why it returns an error.

If you want this to work, you can tell the hash to return another hash instead of "nil" when the key cannot be found:

h = Hash.new({})
h['foo']['bar'] = 1
h['foo'] # => {"bar"=>1}

This semi works, but it's probably better to do a check and manually create the second hash if it doesn't exist.

Railscasts - Free Ruby on Rails Screencasts

Re: Mutli-Dimension Arrays and Recursive Hashes?

There's a great post on this at comp.lang.ruby (from way back on 2001, but it seems things haven't changed since then).

The basic problem you are facing, coming from Perl, is you are expecting autovivification of the hash elements.  Ruby doesn't do that for you, but you can set it up pretty easily, eg:

   class HashMD < Hash 
    def [](n)
       self[n]=HashMD.new if super(n)==nil
       super(n)
    end
  end
  h = HashMD.new
  h['a']['b']['c'] = 'xxx'  #=>  {"a" => {"b" => {"c" => "xxx"}}}

That code is from the thread I meantioned, you might like to read it.  Here it is: http://groups.google.de/group/comp.lang … 04%3F&

Re: Mutli-Dimension Arrays and Recursive Hashes?

It's strange how in the IRB I can just do stuff like that but in Rails I can't.

Any idea as to how I would implement the HashMD class into a Rails application? I see the code, but I am not really sure where I would put that code so I could use it in my application.

Thanks.

Re: Mutli-Dimension Arrays and Recursive Hashes?

You can put it in a file in the "lib" directory in your rails project. You may need to include it as well if it's not working:

# in environment.rb
require "hash_md" # or whatever the file is called in lib

Railscasts - Free Ruby on Rails Screencasts

Re: Mutli-Dimension Arrays and Recursive Hashes?

Thank you both for you help. That worked like a charm... the discussions about both of these classes in the newsgroup went way over my head, but at least arrays work they way I have come to expect now.

Re: Mutli-Dimension Arrays and Recursive Hashes?

right on.  we're all learning here.  smile  In regards the hashMD ... now that you have it, and have it working, you should consider not using it wherever you can.  smile  It's not that it isn't useful, or that it will get in the way with other things (since it is not of type Hash anymore there may be a bit of that but ...) 

-- I think you really want to retool yourself to the "Ruby way of doing things".  (I'm still figuring that out too!)  Part of the charm of working with Rails is the methodology of it: the fact that the language is this extensible is great, that it is so similar to perl just makes it easier for perl/php people to pick up.  But if no one has made autovivication standard in almost 6 years now, there's got to be a good reason for it. 

ryanb's response was the correct response, in that light.

Re: Mutli-Dimension Arrays and Recursive Hashes?

smile

Yea I know what you mean robbiemuffin. This is all very related to my other post (filtering activerecords). I am trying to figure out a good way to pull out something from the DB, put it into an another array/hash/container (lets call it Ha1), and filter Ha1 for certain conditions. I thought it would speed things up to use a Hash (because I could just look for the key and see all items that belong to that key which would be another set of hashes).

Of course a Hash is what a DB index is. I've always been taught that connecting the DB is expensive and should be avoided unless absolutely needed. If I had a choice of asking the DB 200 times for something or creating a hash in the system and then looking up the hash 200 times, I would have picked the hash in PHP. Now are things different enough in Rails that ActiveRecord can handle that many requests in a row without too much of a performance hit? If the answer is yes, then I have learned something new. If the answer is no, then how am I supposed to do things like this?

Thanks for the advice.

Re: Mutli-Dimension Arrays and Recursive Hashes?

eke!  Somebody else could give you a better response about database interfaces in Ruby.  (anybody else could!)

But in general, database hits are not so expensive, unless they are.  (Bear with me)  Say you have a schema like:
table Things
  thing_id
  name
  person_id_ref
  place_id_ref
table People
  person_id
  name
  places_id_ref
table Places
  place_id
  name

So, both places and people have many things.  And places even have many people.

An SQL statement like:
SELECT name FROM People WHERE person_id < 100
that sort of request is just spiffy.  I mean it is fast and hammering your database all day with (meaningful) things like that just makes sense.

Its complex queries that are in general performance hogs.  Like:
SELECT name FROM Things
WHERE place_id_ref IN (SELECT place_id_ref FROM People
WHERE name LIKE 'Abraham%' AND place_id_Ref IN (SELECT place_id FROM Places
WHERE name LIKE 'S%'))

Even this isn't too bad, you should see what kinds of pages of SQL a data mining user can churn out.  smile  Basically, sort and comparison stuff you can do pretty good yourself, so long as what you get back from the database isn't too large.

I think Ruby is fairly against using Views even, and also shies away from stored proceedures.  All those sorts of things should be done (goes the theory) in your code.

Re: Mutli-Dimension Arrays and Recursive Hashes?

The same principle still applies. Try to send as few database queries as possible. At the same time only select the necessary data from the database (not all data). See my reply to your other thread for details.

As for making a hash from an array of models, check out the group_by method. It's really handy:


# select all events in a given month
@event_days = @events.group_by { |event| event.date.day }

That will make a hash, where the key is the day and the value is an array of events for that day.

Railscasts - Free Ruby on Rails Screencasts

Re: Mutli-Dimension Arrays and Recursive Hashes?

Omg... group_by does look handy. If I understand what it does correctly, it is exactly what I was looking for (but I didn't know it at the time)!

Thanks ryanb and robbiemuffin for taking the time to help.

Re: Mutli-Dimension Arrays and Recursive Hashes?

This topic goes some of the way to helping me resolve a problem, but I still have some questions.

I am reading a CSV file into the array @prlines as follows:

@prlines = IO.readlines("#{RAILS_ROOT}/datainputs/manufacturer/#{params[:id]}.#{params[:format]}")

But each row is actually 13 columns of tabular data which i want to parse and then display in a table in the view.

I thought to use the csv library:

def lexmarkpricelisttabularlist
    @prlines = IO.readlines("#{RAILS_ROOT}/datainputs/manufacturer/#{params[:id]}.#{params[:format]}")
    require 'csv'
    CSV::Reader.parse(@prlines) { |row| row.each }
end

But I cannot see what this will produce, or how I can use the output in my view. 

Here is the view with the unparsed @prtable array displayed as raw csv (each line is a string with commas between fields):

<div id="lxpricelist-list">
  <h1>Lx PriceList</h1>

  <table cellpadding="0" cellspacing="0">
  <% for prtable in @prtables %>
    <tr valign="top" class="<%= cycle('list-line-odd', 'list-line-even') %>">
      <td width="60%">
        <span class="lxpricelist-name"><%= h(prtable) %></span>
      </td>
    </tr>
  <% end %>
  </table>
</div>


So I guess I need to replace the code:

<%= h(prtable) %>

with something which lists all 13 columns produced from parsing each row of the file.  But what?

Re: Mutli-Dimension Arrays and Recursive Hashes?

Try this:

<div id="lxpricelist-list">
  <h1>Lx PriceList</h1>

  <table cellpadding="0" cellspacing="0">
  <% for prtable in @prtables %>
    <tr valign="top" class="<%= cycle('list-line-odd', 'list-line-even') %>">
      <% for prcolumn in prtable.split("\t") %>
      <td width="60%">
        <span class="lxpricelist-name"><%= h(prcolumn) %></span>
      </td>
      <% end %>
    </tr>
  <% end %>
  </tab


The split method is key here. It will divide the string into an array by splitting around the specified character.

Railscasts - Free Ruby on Rails Screencasts

Re: Mutli-Dimension Arrays and Recursive Hashes?

Thanks matey - but won't that display each prcolumn element on a new row?  I need to display the 13 elements on the same line in tabular format.  Its really a two dimensional array but i can't see how to create or reference it as such...

Re: Mutli-Dimension Arrays and Recursive Hashes?

SydneyStephen wrote:

Thanks matey - but won't that display each prcolumn element on a new row?

Nope, the <tr> HTML tag (which defines a new row) is outside of the tabular split loop. It should do it the way you want.

Railscasts - Free Ruby on Rails Screencasts

Re: Mutli-Dimension Arrays and Recursive Hashes?

The code produces a single row of all the cells extracted from the csv file instead of many rows of 13 columns.  It may be that there is no end of line character in my csv file or do i need to add some code to tell the second and subsequent rows to start on a new line?

Re: Mutli-Dimension Arrays and Recursive Hashes?

There is an end of line character in the csv file - i am not sure what it is but excel recognises it, as does textmate.

Re: Mutli-Dimension Arrays and Recursive Hashes?

The problem seems to be in my statement:

@prlines = IO.readlines("#{RAILS_ROOT}/datainputs/manufacturer/#{params[:id]}.#{params[:format]}")

which seems to read the whole file into a single row instead of creating an array of rows as I expected.