Prev: Cpp Complier
Next: bug in TkComm::list?
From: Dave Howell on 16 Jul 2010 17:30 On Jul 15, 2010, at 14:45 , Shawn W_ wrote: > Dave Howell wrote: >> I was going to suggest using the 'case' statement instead of all those >> elsifs, but then I realized there was an even better way. >> >> class Array2D >> >> Delta=[[0,0], [0,1], [1,1], [1,0], [1,-1], [0,-1], [-1,-1], [-1,0], >> [-1,1]] >> attr_reader :width, :height >> >> def initialize(width, height) >> @width = width >> @height = height >> @data = Array.new(@width) { Array.new(@height) } >> end >> >> def [](x, y, z) >> deltaX, deltaY = *Delta[z] >> x = x + deltaX % @width >> y = y + deltaY >> @data[x][y] unless y<0 or y>@height >> end >> >> def []=(x, y, z, value) >> deltaX, deltaY = *Delta[z] >> x = (x + deltaX) % @width # modulus % allows wrapping >> y = y + deltaY >> @data[x][y] = value unless y<0 or y>(@height-1) >> end >> end >> >> >> Obviously the Delta array takes the place of the elsif chains in both [] >> and []=. Also, :width and :height are defined with attr_reader, not >> attr_accessor, since it doesn't do any good at all to change those >> values after the array has been created. > > Just be aware I'm a complete newb at programing and Ruby, so the more > elegant the code, the harder it is for me to read. Well, for what it's worth, it could have been 'eleganted' far more unreadably than this. {grin} > David, I tried your solution but it still falls over. Don't you need to > specify what happens if y<0 or y>(@height-1) is the case? I tried... > > if y<0 or y>(@height-1) > nil > else > @data[x][y] = value > end > > ...but that didn't work? It falls over the moment it looks north from > the top row, or looks south east from the bottom row. As you've discovered, "unless" is the same as "if not", and (result) if (test) is the same as if (test) then (result) else nil end Note that you can also do myAnswer = if (test) then (result) else nil end which is the same as if (test) then myAnswer = (result) else myAnswer = nil end So, putting it all together, you could say myAnswer = (result) if (test) If (test) is true, myAnswer will get (result). If it's not true, it will get nil. > > Also, I understand the line... > > deltaX, deltaY = *Delta[z] > > ...is allocating the Delta array values to the deltaX and deltaY > variables based on the direction z given, but what is the * symbol > doing? Splitting the array into pieces. Except that when I tested it just now, it's apparently unnecessary. I didn't know that! > By the way, my program uses a hex grid, not a square grid - I posted a > square grid just for clarity. Adjacent cells directions change for odd > and even rows on a hex grid, but I think I can just create two Delta > arrays (Delta_even and Delta_odd) and modify the 1,0,-1 values for each, > then check for odd/eveness in y. I've worked with hex grids before. You don't need a pair of arrays, you can model a hex grid with a normal 2-d array. Hex: 0=same, 1 = upper right, 2 = right, 3 = lower right, 4 = lower left, 5 = left, 6 = upper left, with [0,0] in the upper left of the grid. Delta=[[0,0], [0.5,-1], [1,0], [0.5,1], [-0.5,1], [-1,0], [-0.5,-1]] If you're in an odd row (y%2 ==1), then add 0.75 to x. If you're in an even row, add 0.25. Then drop the decimal part to get your x,y values. x = (x + deltaX + 0.25 + (0.5 if y%2 ==1).to_f).to_i The ".to_f" is needed because if y%2 is NOT == 1, the result is "nil", and you can't add nil to a number, so the .to_f converts 'nil' to "0.0". Imagine each co-ordinate is a brick in a normal wall of bricks. Every other row is slid over a bit, so you have to zig-zag in order to go 'straight down" the wall. You might need to play around with it to see why it works . . . > The error I'm getting is... > > undefined method `[]=' for nil:NilClass (NoMethodError) > > ...when the program hits this part of the code... > > Array2D[x,y,1] = "X " I don't consider myself a hard-core Ruby expert, so I wouldn't be surprised if there was a bug in my code. On the other hand, I'll bet part of the problem is that you appear to be confusing a Class with an instance. You can't (or shouldn't normally be able to) assign anything to "Array2D". myTerritory = Array2D.new myTerritory[3,4,1] = "X " enemyTerritory = Array2D.new enemyTerritory[2,3,1] = "X " Now, it *is* possible to define Array2D in a way that would let you say something like Array2D[x,y,1] = "X " and have it work, but we haven't done that here and I don't think you want to. There are . . . ramifications . . .
From: Dave Howell on 16 Jul 2010 17:44 On Jul 16, 2010, at 1:24 , Martin DeMello wrote: > > The basic problem is that ruby doesn't have rectangular arrays, you > have to use arrays of arrays. > So what happens when you go off the end of the array? Hmm. I think you need to re-read my example, because my code *never accesses an out of bounds array*. My code does NOT take advantage of array[tooBig] == nil. Instead, it returns nil if Y is out of bounds INSTEAD of checking the array. I think the error's being raised because he's trying to assign to the class, not an instance.
From: Shawn W_ on 16 Jul 2010 19:39 Martin DeMello wrote: > So what happens when you go off the end of the array? Calling array[n] > where n is out of bounds will return nil, as expected... > > array[1][100] # => nil > > which is equivalent to > > a = array[1] #=> [..., ..., ...] > a[100] #=> nil > > But if you go out of bounds in the other dimension > > array[100][1] # => undefined method `[]=' for nil:NilClass > (NoMethodError) > > because what you're in effect doing is > > a = array[100] #=> nil > a[1] # => trying to call [] on nil, which raises the error > > martin Understood. Does that mean it should work if I wanted to wrap top and bottom, and cap left and right, rather than the other way around? I've tried this but it also fell over with the same error. I would think that if array[100][1] won't work for the reasons you give above, array[1][100] should work, returning nil? Perhaps I could write a custom method for the nil class? Other than that, I don't know how to get around this - maybe investigate NArray, or add rows top and bottom that are invisible, and absorb all top/bottom out of bounds queries. Dave Howell wrote: > Well, for what it's worth, it could have been 'eleganted' far more > unreadably than this. {grin} As a beginner, I don't mind elegant code, as long as the longer version is also presented to compare. If you can't do this, long version. And if not that, then, yes, elegant is fine and I'll just have to nut it out - better than nothing at all. Your code was only abstracted a little from mine, so understandable even to me, although for 5 seconds I thought, eek. > I've worked with hex grids before... Thx. That's helful. I will go through it properly once I get my capping. It makes sense at first glance. >> Array2D[x,y,1] = "X " > > I don't consider myself a hard-core Ruby expert, so I wouldn't be > surprised if there was a bug in my code. On the other hand, I'll bet > part of the problem is that you appear to be confusing a Class with an > instance. You can't (or shouldn't normally be able to) assign anything > to "Array2D". My new array is not called Array2D in my program. My mistake to write it as I did. Should have used new_array perhaps. new_array = Array2D(16,8) new_array[6,0,1] = "X " You did leave out a few brackets, and forgot one of the -1's next @height. I fixed those and as far as I could see there wasn't anything wrong with it, but it's not like I'd know. -- Posted via http://www.ruby-forum.com/.
From: Martin DeMello on 16 Jul 2010 20:17 On Sat, Jul 17, 2010 at 5:09 AM, Shawn W_ <shawnw(a)internode.on.net> wrote: > > Does that mean it should work if I wanted to wrap top and bottom, and > cap left and right, rather than the other way around? I've tried this > but it also fell over with the same error. I would think that if > array[100][1] won't work for the reasons you give above, array[1][100] > should work, returning nil? > > Perhaps I could write a custom method for the nil class? Other than > that, I don't know how to get around this - maybe investigate NArray, or > add rows top and bottom that are invisible, and absorb all top/bottom > out of bounds queries. No, the simplest thing to do is what you've already done - write an Array2D class to hold your array of arrays, and do some checking when you dispatch [] class Array2D attr_reader :rows, :columns def initialize(m, n) @rows = m @columns = n @array = Array.new(@rows) { Array.new(@columns) } end def [](col, row) i = col % columns j = row % rows @array[j][i] end def []=(col, row, val) i = col % columns j = row % rows @array[j][i] = val end end you have the actual array, @array, as a private variable, so the only way to access it is through your Array2D object, and your definitions of [] and []= make sure it wraps in both directions. ( % is the mod operator ) If you'd rather return nils for out of bounds: def [](col, row) if col < 0 or col > (columns - 1) or row < 0 or row > (rows - 1) return nil end @array[row][col] end and then you need to decide what to do when assigning out of bounds. martin
From: Dave Howell on 16 Jul 2010 22:44
OK, let's try this from the top. {grin} Here's some fresh new code: class HexArray Delta=[[0,0], [0.5,-1], [1,0], [0.5,1], [-0.5,1], [-1,0], [-0.5,-1]] attr_reader :width, :height def initialize(width, height, default="-") @width = width @height = height @data = Array.new(@width){|row| [default] * @height} end def [](x, y, z) col, row = move(x,y,z) @data[row][col] unless (row<0 or row>(@height-1)) end def []=(x, y, z, value) col, row = move(x,y,z) @data[row][col] = value unless (row<0 or row>(@height-1)) end def inspect @height.times{|rownum| print (" " * (rownum % 2) + @data[rownum].join(" ") + "\n")};nil end private def move(x, y, z) # returns an array with the row and column of the new position deltaX, deltaY = Delta[z] [(x + deltaX + 0.25 + (0.5 * (y%2)) % @width).floor, y + deltaY] end end Instead of repeating the math for figuring out the row and column, I moved it to the "move" method. I also created an 'inspect' method so we can see what we're doing. The math in the 'move' method is all packed together, but it's just what I talked about earlier, except with all the parentheses in the right places to make it work correctly. Note that @data is no longer defined as "Array.new(@width) { Array.new(@height) }". I think I copied that from your code? but discovered that it gave each row the SAME array, so that changing [1][3] caused every cell in that column to have the same value. Useless! Not the first time that feature of Ruby (and it is a feature, wacky though it sometimes seems) has surprised me. Let's take it for a spin. >> a=HexArray.new(5,5) - - - - - - - - - - - - - - - - - - - - - - - - - => >> a[0,0,0] = "A" # upper left => "A" >> a[4,4,0] = "Z" # lower right => "Z" >> a[0,2,0] = "J" # center left => "J" >> a[2,0,0] = "S" # center top => "S" >> a[0,2,2] = "K" # right of J => "K" >> a[2,0,3] = "T" # below S and to the right => "T" >> a[0,2,5] = "I" # left of J (should wrap) => "I" >> a[2,0,1] = "R" # above & right of S (should be ignored) => "R" >> a[4,4,4] = "X" # below and left of Z (should be ignored) => "X" >> a A - S - - - - T - - J K - - I - - - - - - - - - Z => >> a[0,2,4] = "M" # left and below J (should wrap) => "M" >> a A - S - - - - T - - J K - - I - - - - M - - - - Z => >> ?> a[4,2,0] #should be position "I" => "I" >> a[4,2,3] = "N" # right and below "I", should overwrite "M" => "N" >> a A - S - - - - T - - J K - - I - - - - N - - - - Z => Also, I set it up so that a new HexArray gets filled with dashes. If you want to fill it will asterisks, or nils, just do this: a = Array2D(5,5,"*") or a = Array2D(5,5,nil) |