Use loop counter for 2D array index

Noob: I’m trying to fill a 2D array using a loop counter as the index, but I can’t figure out the finer syntax.

The code is along the lines of:

data = Array[[],[]]
counter_max = 750

for counter in 0..counter_max
  itemX = #calculation X
  itemY = #calculation Y
  itemZ = #calculation Z

  data[counter][0] = itemX
  data[counter][1] = itemY
  data[counter][2] = itemZ
end

When I run this, I get the error “undefined method ‘=’ for nil:NilClass>”

If I replace the ‘[counter]’ index reference with [0] then I don’t get an error message. Also, if I use the same logic, but with several 1D arrays it works; so I know my basic syntax is right. I just can’t figure out why using the ‘counter’ variable causes an error with a 2D array.

Any help would be appreciated.

Thanks!

Not sure if I understand what you are trying to achieve but it seems that you have the index of the array in the assignment inverted. This works:

data = Array[[], [], []] # Were you missing an index?
counter_max = 10

for counter in 0..counter_max
  itemX = counter / 2 # Any operation
  itemY = counter * 2 # Any operation
  itemZ = counter ** 2 # Or maybe you didn't need the third index?

  data[0][counter] = itemX
  data[1][counter] = itemY
  data[2][counter] = itemZ
end

More compact:

newdata = Array[[], [], []]
newdata[0] = (0..counter_max).map{|c| c / 2}
newdata[1] = (0..counter_max).map{|c| c * 2}
newdata[2] = (0..counter_max).map{|c| c ** 2}

Why not use a single array, to contain an array like a ‘point’ ?
Perhaps easier to understand split up like this…

data=[]
max = 10
0..max{|c|
  arr=[]
  arr << c/2.0   ### x
  arr << c*2.0   ### y
  arr << c**2.0  ### z
  data << arr
}

Note how I’ve used a float for the math - an integer produces only integer results 1/2 >>> 0 but 1/2 >>> 0.5

Now because it’s a three element array data[i].x returns the X [or .y for Y or ,z for Z] value for index ‘i’…

1 Like

Nice. Didn’t now that the methods .x, .y and .z could be used for normal arrays as well. Very handy, thanks!

Based on the way the array data was originally defined I was assuming that he needed list of coordinates and not list of ‘points’, but this is probably not the case.

Those operations were just examples. After several years working with numerical methods, I am well aware of the issues with floating point arithmetic :smile:

The additional methods .x, .y and .z are added to Array by SketchUp, because a three-element Array can be used instead of a Point3d in many cases, and the matching methods also exist in that too…

Thanks for your answers guys. As I’ve only been teaching myself Ruby for less than a week, I don’t know what it all means…but I’ll get there! To further flesh out what I’m trying to achieve, I’ve attached a .jpg and the .rb file that drew it.
su_bentspring.rb (1.3 KB)

1 Like

Well, you could get ten Ruby coders together, and they’d all code a problem in a different way. But since you mentioned that you’re just starting, I made a few changes and added another loop to create a ‘tube’ with faces.

A few things –

  • when making alot of changes to a model, wrap the operations with start_operation & commit_operation statements, which places a single entry in the ‘Edit - Undo’ list.

  • Not sure why you were storing the points for the spring. They aren’t really needed, just the last point, which the ‘spring’ operation in my code shows.

  • Ruby has many ways to run loops, I used an upto loop for my code.

  • I changed the calcs a bit for fewer ops.

Nice code for the spring. Now lets see it using transforms, so it can be started and stopped anywhere in space. Just kidding…

bent_spring_tube.rb (2.3 KB)

Greg

Thanks for your answer Greg - always insightful to see how other coders do things. Thanks for the tips you offer too - they are handy things to know.

As far as the faces are concerned, I was more interested in a spring-like form than a tube form - but that’s ok. I’ve found the “Face.followme” method, so I’ll look into that.

Thanks once again for your help.

The code, data = Array[[],[]]
… actually creates an array with only two starting members, that are empty array objects.

This means they have members [0] and [1], and everything was fine the first two times through your loop.

The third time through, however when you were calling data[2] the Array#[] method returned nil because an index beyond the member range was passed. And the second subscript (index) call of [0]= is really a call of nil[0]=, which causes the NoMethodError, because the NilClass does not have a method named “[]=”.

It’s a quirk of Ruby that nil gets returned in cases like this, because index methods double as existence query methods. It’s as if you are asking first IF member 2 of data exits, return it otherwise return nil. In Ruby it is customary to test return values for “nilness” or boolean true, as nil gets logically evaluated as FALSE. (You will see this also in IO methods, where 'nil is returned if the user cancels, or a file object does not exist.)
So after calling such methods, you do a boolean test before using the return value…

value = data[i]
if value
  # use value
else
  # recover gracefully, exit method, etc.
end

You can initialize an array with n number of nested empty arrays thus:

data = Array::new(n) {|i| Array::new }

(In this case, the index i is unused inside the block.)


Second your original loop would have had 751 iterations. You do not really have to use a max counter like that.

In Ruby can just do:

for counter in data_array
  # code
end

… and it will only iterate what members actually exist in the array.
It is similar to

data_array.each do |counter|
  # code
end

The for loop is slightly faster, as it does not create a new variable scope with every iteration. The each method does create a new scope.


As a newb, you may not realize that the Enumerable mixin module, is mixed into the Array and Hash classes, (as well as most of the SketchUp API’s collection classes.) This mixin library adds many useful iteration and query methods.


Good primer docs are listed here:
Index of Files, Classes & Methods in Ruby 2.7.2 (Ruby 2.7.2)
You can ignore the few at the top, beginning with “.lib” and “.test”.
Read the one on globals, then skip all the Rake docs.
Start again with the “re.rdoc” and down to the end of the list.

3 Likes

Good stuff Dan (you actually answered my original query - thanks for that!)

This has all been very informative. Feeling quite inadequate atm, but that’s normal when learning something new.