Multi-dimensional arrays
Posted: Sat May 25, 2013 10:57 pm
Hi All,
There have been a few questions lately that bring up the subject of multi-dimensional arrays. This is something that we never had before Ruby, and is pretty tricky to implement using the 'green' primitives.
In fact, I would go so far as to say that, although the funky vector graphics, .dll access etc. are all very tasty Ruby features, the ability to build proper data structures is the real 'killer app' - whether you want to store a load of MIDI notes for a sequence, create a song database, or store tabulated results from your number crunching, Ruby will handle it like a breeze.
First a quick intro to Ruby's arrays and hashes for those who have not had a chance to play with them yet...
Arrays - Aside from the fact that there are zillions of different built-in methods for manipulating arrays, they have one great feature that 'green' arrays don't - they can hold just about ANY kind of data, and you can mix different kinds of data within the same array. No longer do you have to restrict yourself to all-integer, all-float, or all-string contents.
And this is crucial to multi-dimensional arrays...
Ruby doesn't really have multi-dimensional arrays at all - you can't for example, declare an array in the form "Array[10,20]", as you might in many other languages. But, Ruby arrays can contain other sub-arrays as data items - and you can keep nesting them inside each other as far as you want. This is the key to multi-dimensional arrays, and in some ways is more versatile than the more common kind, as you can easily extract whole "rows" of data and treat them as a single object (because an Array is a single object in Ruby). the structure ends up a little like this...
main_array = [ row1_array, row2_array, row3_array]
Where each 'row_array' holds a series of values representing each column of that row.
To get at a row, use its index...
this_row = main_array[x]
..and then get the column within the row...
result = this_row[y]
This can be simplified to...
result = main_array[x][y] # two sets of brackets! NOT [x,y]
Hashes - hashes don't exist in 'green', but once understood open up some great new possibilities. In a hash, there are no numbered indexes, and items are not stored in any particular order - but you can give each item a 'key' to reference it with, and Ruby is pretty flexible about what you can use as keys. Here's a very quick example using strings as the keys...
Why do I mention hashes when I am supposed to be showing you arrays? Well, because there is a sneaky way to make a Hash behave like a multi-dimensional array. It is not as versatile as the 'nested sub-array' method, but it needs a little less code and can be slightly more efficient.
And now for the examples - these are fully functioning modules, so can be plugged into your 'green' circuits if Ruby is not your cup of tea...
There are two methods shown in here for making a two-dimensional array - with a 'maker' and a 'getter' for each...
- The top one is what you might call the 'canonical' method, and is the one that most Rubyists would recommend for most typical cases. It is the one most compatible with Ruby's many array methods for sorting, filtering etc., and the array itself has a form which is intuitive to view.
The main point to watch out for is the way that empty elements are dealt with - making sure that each element in the outer array has a valid sub-array for each of its 'rows', and getting a valid 'default' value to come out for 'cells' which haven't been assigned yet.
- The lower one shows it done using a Hash - it's not a multi-dimensional array at all in reality, but the hash uses keys which encode the 'X' and 'Y' indexes in such a way that it behaves exactly the same as the top example, and is much leaner code. However, it does restrict the ways in which you can process the data - in particular, there is no simple way to access a whole array row or column, and counting through the array in order is much trickier without Ruby's cool Array handling methods. But still, it could be handy if you have a particularly large array that only requires the simplest of 'set' and 'get' functions, and you need the very fastest code possible.
Note also that you can't mix and match these two methods. Although all Ruby 'value' connectors look the same, they are able to carry any kind of Ruby object, so it is up to the programmer to be sure that the output from one Ruby primitive matches the input that the other one expects to see - giving the 'hash' style getter an array as its input will just confuse it!
There have been a few questions lately that bring up the subject of multi-dimensional arrays. This is something that we never had before Ruby, and is pretty tricky to implement using the 'green' primitives.
In fact, I would go so far as to say that, although the funky vector graphics, .dll access etc. are all very tasty Ruby features, the ability to build proper data structures is the real 'killer app' - whether you want to store a load of MIDI notes for a sequence, create a song database, or store tabulated results from your number crunching, Ruby will handle it like a breeze.
First a quick intro to Ruby's arrays and hashes for those who have not had a chance to play with them yet...
Arrays - Aside from the fact that there are zillions of different built-in methods for manipulating arrays, they have one great feature that 'green' arrays don't - they can hold just about ANY kind of data, and you can mix different kinds of data within the same array. No longer do you have to restrict yourself to all-integer, all-float, or all-string contents.
And this is crucial to multi-dimensional arrays...
Ruby doesn't really have multi-dimensional arrays at all - you can't for example, declare an array in the form "Array[10,20]", as you might in many other languages. But, Ruby arrays can contain other sub-arrays as data items - and you can keep nesting them inside each other as far as you want. This is the key to multi-dimensional arrays, and in some ways is more versatile than the more common kind, as you can easily extract whole "rows" of data and treat them as a single object (because an Array is a single object in Ruby). the structure ends up a little like this...
main_array = [ row1_array, row2_array, row3_array]
Where each 'row_array' holds a series of values representing each column of that row.
To get at a row, use its index...
this_row = main_array[x]
..and then get the column within the row...
result = this_row[y]
This can be simplified to...
result = main_array[x][y] # two sets of brackets! NOT [x,y]
Hashes - hashes don't exist in 'green', but once understood open up some great new possibilities. In a hash, there are no numbered indexes, and items are not stored in any particular order - but you can give each item a 'key' to reference it with, and Ruby is pretty flexible about what you can use as keys. Here's a very quick example using strings as the keys...
Code: Select all
@my_hash = { 'name'=>'Trog', 'avatar'=>'Ugly Caveman.bmp', 'age'=>43 }
@my_hash['name'] # result: 'Trog'Why do I mention hashes when I am supposed to be showing you arrays? Well, because there is a sneaky way to make a Hash behave like a multi-dimensional array. It is not as versatile as the 'nested sub-array' method, but it needs a little less code and can be slightly more efficient.
And now for the examples - these are fully functioning modules, so can be plugged into your 'green' circuits if Ruby is not your cup of tea...
There are two methods shown in here for making a two-dimensional array - with a 'maker' and a 'getter' for each...
- The top one is what you might call the 'canonical' method, and is the one that most Rubyists would recommend for most typical cases. It is the one most compatible with Ruby's many array methods for sorting, filtering etc., and the array itself has a form which is intuitive to view.
The main point to watch out for is the way that empty elements are dealt with - making sure that each element in the outer array has a valid sub-array for each of its 'rows', and getting a valid 'default' value to come out for 'cells' which haven't been assigned yet.
- The lower one shows it done using a Hash - it's not a multi-dimensional array at all in reality, but the hash uses keys which encode the 'X' and 'Y' indexes in such a way that it behaves exactly the same as the top example, and is much leaner code. However, it does restrict the ways in which you can process the data - in particular, there is no simple way to access a whole array row or column, and counting through the array in order is much trickier without Ruby's cool Array handling methods. But still, it could be handy if you have a particularly large array that only requires the simplest of 'set' and 'get' functions, and you need the very fastest code possible.
Note also that you can't mix and match these two methods. Although all Ruby 'value' connectors look the same, they are able to carry any kind of Ruby object, so it is up to the programmer to be sure that the output from one Ruby primitive matches the input that the other one expects to see - giving the 'hash' style getter an array as its input will just confuse it!