Return nothing when looking outside the bounds of 2D array?

I have a 2D Array. I have written a method
Array2D.adjacent(x,y,direction) that returns the adjacent cell to x,y in
the direction given. How do I deal with the boundary conditions without
receiving an error message.

For example, if I refer to a cell on the top row, and look north there
will be nothing there, and my program falls over.

I will be creating methods that run over the whole 2D array, replacing
things in random directions, so when it randomly hits a boundary I need
my program to ignore cells outside the boundary.

How can I do this? Thx

···

--
Posted via http://www.ruby-forum.com/.

Yields nil if out of bounds:

if ary[y]
  ary[y]
else
  nil
end

···

On Jul 14, 3:56 pm, Shawn W_ <sha...@internode.on.net> wrote:

I have a 2D Array. I have written a method
Array2D.adjacent(x,y,direction) that returns the adjacent cell to x,y in
the direction given. How do I deal with the boundary conditions without
receiving an error message.

For example, if I refer to a cell on the top row, and look north there
will be nothing there, and my program falls over.

I will be creating methods that run over the whole 2D array, replacing
things in random directions, so when it randomly hits a boundary I need
my program to ignore cells outside the boundary.

How can I do this? Thx
--
Posted viahttp://www.ruby-forum.com/.

Did anybody suggest modulo operator so far? I only glanced over
replies so I might actually have overseen something. Anyway, you can
see it working here:

irb(main):001:0> a = Array.new(10){|row| Array.new(10) {|col|
"#{row},#{col}"}};nil
=> nil
irb(main):002:0> a.size
=> 10
irb(main):003:0> a[0].size
=> 10

irb(main):012:0> 15.times {|i| printf "%2d %p %p\n",i,a[0][i % 10],a[i % 10][0]}
0 "0,0" "0,0"
1 "0,1" "1,0"
2 "0,2" "2,0"
3 "0,3" "3,0"
4 "0,4" "4,0"
5 "0,5" "5,0"
6 "0,6" "6,0"
7 "0,7" "7,0"
8 "0,8" "8,0"
9 "0,9" "9,0"
10 "0,0" "0,0"
11 "0,1" "1,0"
12 "0,2" "2,0"
13 "0,3" "3,0"
14 "0,4" "4,0"
=> 15

Kind regards

robert

···

2010/7/14 Shawn W_ <shawnw@internode.on.net>:

I have a 2D Array. I have written a method
Array2D.adjacent(x,y,direction) that returns the adjacent cell to x,y in
the direction given. How do I deal with the boundary conditions without
receiving an error message.

For example, if I refer to a cell on the top row, and look north there
will be nothing there, and my program falls over.

I will be creating methods that run over the whole 2D array, replacing
things in random directions, so when it randomly hits a boundary I need
my program to ignore cells outside the boundary.

How can I do this? Thx

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Thx. Don't quite understand that code. I tried plugging in some nils but
didn't work. Do I just need to assign nil values to the cells outside
the bounds of my array for the program to ignore them?

Here's what I've got so far. At the moment everything is wrapping. I
need it to wrap left and right, but to be capped top and bottom.

class Array2D

  attr_accessor :width, :height

  def initialize(width, height)
    @width = width
    @height = height
    @data = Array.new(@width) { Array.new(@height) }
  end

  # Returns adjacent cell of given 2D array index x,y, in the given
direction z, where z = 0 is no direction (the cell in the middle), and z
= 1-8 are the surrounding cells.
  def [](x, y, z)
    x = x % @width # modulus % allows wrapping
    y = y % @height
    if z == 0
      @data[x][y]
      elsif z == 1
        @data[x][y+1]
      elsif z == 2
        @data[x+1][y+1]
      elsif z == 3
        @data[x+1][y]
      elsif z == 4
        @data[x+1][y-1]
      elsif z == 5
        @data[x][y-1]
      elsif z == 6
        @data[x-1][y-1]
      elsif z == 7
        @data[x-1][y]
      elsif z == 8
        @data[x-1][y+1]
    end
  end

  # Allows the allocation of values to adjacent cell of given 2D array
index x,y, in the given direction z, where z = 0 is no direction (the
cell in the middle), and z = 1-8 are the surrounding cells.
  def []=(x, y, z, value)
    x = x % @width # modulus % allows wrapping
    y = y % @height
    if z == 0
      @data[x][y] = value
      elsif z == 1
        @data[x][y+1] = value
      elsif z == 2
        @data[x+1][y+1] = value
      elsif z == 3
        @data[x+1][y] = value
      elsif z == 4
        @data[x+1][y-1] = value
      elsif z == 5
        @data[x][y-1] = value
      elsif z == 6
        @data[x-1][y-1] = value
      elsif z == 7
        @data[x-1][y] = value
      elsif z == 8
        @data[x-1][y+1] = value
    end
  end

end

  # This method replaces contents of 9 cells in a square configuration
at a random location on the 2D array.
  def form_plates
    x = rand(@cols)
    y = rand(@rows)
    Array2D[x,y,0] = "X "
    Array2D[x,y,1] = "X "
    Array2D[x,y,2] = "X "
    Array2D[x,y,3] = "X "
    Array2D[x,y,4] = "X "
    Array2D[x,y,5] = "X "
    Array2D[x,y,6] = "X "
    Array2D[x,y,7] = "X "
    Array2D[x,y,8] = "X "
  end

···

--
Posted via http://www.ruby-forum.com/.

def get2d ary, x, y
  if ary[y]
    ary[y]
  else
    nil
  end
end

ary = [
  %w(Z Z Z Z),
  %w(Z H H Z),
  %w(Z X Z Z) ]

puts get2d( ary,2,1 )
puts get2d( ary,1,2 )
puts get2d( ary,3,3 )
puts get2d( ary,4,0 )

--- output ---
H
X
nil
nil

···

On Jul 14, 6:07 pm, w_a_x_man <w_a_x_...@yahoo.com> wrote:

On Jul 14, 3:56 pm, Shawn W_ <sha...@internode.on.net> wrote:

> I have a 2D Array. I have written a method
> Array2D.adjacent(x,y,direction) that returns the adjacent cell to x,y in
> the direction given. How do I deal with the boundary conditions without
> receiving an error message.

> For example, if I refer to a cell on the top row, and look north there
> will be nothing there, and my program falls over.

> I will be creating methods that run over the whole 2D array, replacing
> things in random directions, so when it randomly hits a boundary I need
> my program to ignore cells outside the boundary.

> How can I do this? Thx
> --
> Posted viahttp://www.ruby-forum.com/.

Yields nil if out of bounds:

if ary[y]
ary[y]
else
nil
end

A better way to describe it.

Here's a 16 x 8 array of Z's, with the Z replaced with X and H's like
so:

Array2D[6,3,0] = "X "
Array2D[6,3,1] = "H " # direction 1 (north) from array index 6,3
Array2D[6,3,2] = "H " # direction 2 (north east) from array index 6,3
Array2D[6,3,3] = "H "
Array2D[6,3,4] = "X "
Array2D[6,3,5] = "X "
Array2D[6,3,6] = "X "
Array2D[6,3,7] = "X "
Array2D[6,3,8] = "X "

Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z X H H Z Z Z Z Z Z Z Z
Z Z Z Z Z X X H Z Z Z Z Z Z Z Z
Z Z Z Z Z X X X Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z

What I need is Array2D[6,0,1] = "H ", for example, not to make my
program fall over (array index 6,0 in direction 1 (north) sits outside
my array).

At the moment it wraps top and bottom like so:

Z Z Z Z Z X X H Z Z Z Z Z Z Z Z
Z Z Z Z Z X X X Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z X H H Z Z Z Z Z Z Z Z

If I remove the top/bottom wrapping, I need it to just cut it off at the
top, like so:

Z Z Z Z Z X X H Z Z Z Z Z Z Z Z
Z Z Z Z Z X X X Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z

···

--
Posted via http://www.ruby-forum.com/.

def get2d ary, x, y
  if ![x,y].include?(-1) and ary[y]
    ary[y]
  else
    nil
  end
end

ary = [
  %w(Z Z Z Z),
  %w(Z H H Z),
  %w(Z X Z Z) ]

puts get2d( ary,2,1 )
puts get2d( ary,1,2 )
puts get2d( ary,3,3 )
puts get2d( ary,4,0 )
puts get2d( ary,4,-1 )

--- output ---
H
X
nil
nil
nil

···

On Jul 15, 5:49 am, w_a_x_man <w_a_x_...@yahoo.com> wrote:

On Jul 14, 6:07 pm, w_a_x_man <w_a_x_...@yahoo.com> wrote:

> On Jul 14, 3:56 pm, Shawn W_ <sha...@internode.on.net> wrote:

> > I have a 2D Array. I have written a method
> > Array2D.adjacent(x,y,direction) that returns the adjacent cell to x,y in
> > the direction given. How do I deal with the boundary conditions without
> > receiving an error message.

> > For example, if I refer to a cell on the top row, and look north there
> > will be nothing there, and my program falls over.

> > I will be creating methods that run over the whole 2D array, replacing
> > things in random directions, so when it randomly hits a boundary I need
> > my program to ignore cells outside the boundary.

> > How can I do this? Thx
> > --
> > Posted viahttp://www.ruby-forum.com/.

> Yields nil if out of bounds:

> if ary[y]
> ary[y]
> else
> nil
> end

def get2d ary, x, y
if ary[y]
ary[y]
else
nil
end
end

ary = [
%w(Z Z Z Z),
%w(Z H H Z),
%w(Z X Z Z) ]

puts get2d( ary,2,1 )
puts get2d( ary,1,2 )
puts get2d( ary,3,3 )
puts get2d( ary,4,0 )

--- output ---
H
X
nil
nil

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.

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[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[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.

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[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.

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?

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 have yet to nut out the other solution but will go through it later
today.

···

--
Posted via http://www.ruby-forum.com/\.

Okay, just found out that...

@data[x][y] unless y<0 or y>@height

...is the same as stating nil anyway, according to
http://www.themomorohoax.com/2009/05/21/when-to-use-nil-in-ruby-methods

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 "

···

--
Posted via http://www.ruby-forum.com/.

The basic problem is that ruby doesn't have rectangular arrays, you
have to use arrays of arrays. So "array[y]" means "get array[y]
(which is itself an array) and take the xth element of that". So let's
say you initialize your array to [4][3]. Then you have

array = [
[nil, nil, nil], # array[0]
[nil, nil, nil], # array[1]
[nil, nil, nil], # array[2]
[nil, nil, nil] # array[3]
]

Now say you set array[1][2] = "X". Your new array is

array = [
[nil, nil, nil], # array[0]
[nil, nil, X ], # array[1]
[nil, nil, nil], # array[2]
[nil, nil, nil] # array[3]
]

you can do this either via

array[1][2] = "X"

or

a = array[1]
a[2] = "X"

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

···

On Fri, Jul 16, 2010 at 11:11 AM, Shawn W_ <shawnw@internode.on.net> wrote:

Okay, just found out that...

@data[y] unless y<0 or y>@height

...is the same as stating nil anyway, according to
themomorohoax.com - This website is for sale! - themomorohoax Resources and Information.

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 "

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[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[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[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 . . .

···

On Jul 15, 2010, at 14:45 , Shawn W_ wrote:

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.

···

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?

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/\.

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

···

On Sat, Jul 17, 2010 at 5:09 AM, Shawn W_ <shawnw@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.

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)

OK, let's try this from the top. {grin} Here's some fresh new code:

Looks good :slight_smile:

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.

That shouldn't have happened - the block passed to Array.new should
create a new array every time it's called.

irb(main):001:0> a = Array.new(5) { Array.new(3) }
=> [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil], [nil, nil,
nil], [nil, nil, nil]]

irb(main):002:0> a[1][2] = 0
=> 0

irb(main):003:0> a
=> [[nil, nil, nil], [nil, nil, 0], [nil, nil, nil], [nil, nil, nil],
[nil, nil, nil]]

irb(main):004:0> b = Array.new(5, Array.new(3))
=> [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil], [nil, nil,
nil], [nil, nil, nil]]

irb(main):005:0> b[1][2] = 0
=> 0

irb(main):006:0> b
=> [[nil, nil, 0], [nil, nil, 0], [nil, nil, 0], [nil, nil, 0], [nil, nil, 0]]

martin

···

On Sat, Jul 17, 2010 at 8:14 AM, Dave Howell <groups.2009a@grandfenwick.net> wrote:

Dave Howell wrote:

OK, let's try this from the top. {grin} Here's some fresh new code:

Thx again - very instructive.

I have managed to get the capping working in isolation as per the
following code:

···

---

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
  attr_accessor :cell_content

  def initialize(width, height, cell_content)
    @width = width
    @height = height
    @data = Array.new(@height) {Array.new(@width){|i| cell_content} }
  end

  def (x, y, z)
    deltaX, deltaY = *Delta[z]
    x = (x + deltaX) % @width
    y = (y + deltaY)
    @data[y] unless y<0 or y>(@height-1)
  end

  def =(x, y, z, value)
    deltaX, deltaY = *Delta[z]
    x = (x + deltaX) % @width # modulus % allows wrapping
    y = (y + deltaY)
    @data[y] = value unless y<0 or y>(@height-1)
  end

  def display
    @height.times {|rownum| print(@data[rownum].join(" ") + "\n")};nil
  end

end

a=Array2D.new(10,5,"X")
m = rand(10)
n = rand(5)
a[m,n,0] = "A"
a[m,n,1] = "H"
a[m,n,2] = "H"
a[m,n,3] = "H"
a[m,n,4] = "A"
a[m,n,5] = "A"
a[m,n,6] = "A"
a[m,n,7] = "A"
a[m,n,8] = "A"
puts a.display

---

However, it still falls over when I try to insert the above into my main
program, as below:

---

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(@height) {Array.new(@width) {|i|"X"}}
  end

  def (x, y, z)
    deltaX, deltaY = *Delta[z]
    x = (x + deltaX) % @width
    y = (y + deltaY)
    @data[y] unless y<0 or y>(@height-1)
  end

  def =(x, y, z, value)
    deltaX, deltaY = *Delta[z]
    x = (x + deltaX) % @width # modulus % allows wrapping
    y = (y + deltaY)
    @data[y] = value unless y<0 or y>(@height-1)
  end

  def display
    @height.times {|rownum| print(@data[rownum].join(" ") + "\n")};nil
  end

end

class World < Array2D

  attr_reader :size

  def initialize(size)
    @rows = size
    @cols = @rows * 2
    @world = Array2D.new(@cols, @rows)
  end

  def populate_array_cells_with_hash
    (0...@rows).each do |b|
      (0...@cols).each do |a|
        @world[a,b,0] = {
        "hex_col_no" => a,
        "hex_row_no" => b,
        "hex_id_A" => "X ",
        "hex_id_B" => nil,
        "hex_id_C" => nil,
        "hex_id_D" => nil,
        "hex_id_E" => nil,
        }
      end
    end
  end

  def insert_teleporting_square
    m = rand(@cols)
    n = rand(@rows)
    @world[m,n,0]["hex_id_A"] = "A "
    @world[m,n,1]["hex_id_A"] = "H "
    @world[m,n,2]["hex_id_A"] = "H "
    @world[m,n,3]["hex_id_A"] = "H "
    @world[m,n,4]["hex_id_A"] = "A "
    @world[m,n,5]["hex_id_A"] = "A "
    @world[m,n,6]["hex_id_A"] = "A "
    @world[m,n,7]["hex_id_A"] = "A "
    @world[m,n,8]["hex_id_A"] = "A "
  end

  def print_world(type)
    (0...@rows).each do |b|
      puts
        (0...@cols).each do |a|
          print @world[a,b,0][type]
        end
    end
  end

end

first_world = World.new(5)
first_world.populate_array_cells_with_hash
first_world.insert_teleporting_square
first_world.print_world("hex_id_A")
puts

---

It falls over when it hits one of these two lines:
@world[m,n,1]["hex_id_A"] = "H "
@world[m,n,4]["hex_id_A"] = "A "

With error as before:
undefined method `=' for nil:NilClass (NoMethodError)

What my program does is generate an array, then populates each cell of
that array with a hash. It looks like when it tries to access the hash
values outside the array boundary it runs into problems? Do I need
another method inside the Array2D class to deal with hash access? If so,
how can this be done? If not, can anyone see any other reason for the
error?

--
Posted via http://www.ruby-forum.com/\.

Shawn W_ wrote:

Dave Howell wrote:

OK, let's try this from the top. {grin} Here's some fresh new code:

What my program does is generate an array, then populates each cell of
that array with a hash. It looks like when it tries to access the hash
values outside the array boundary it runs into problems? Do I need
another method inside the Array2D class to deal with hash access? If so,
how can this be done? If not, can anyone see any other reason for the
error?

I think I got it. I did away with storing my cell data in a hash; rather
I will put an array in each cell of the 2D array, and add another
parameter to the def and def = methods to access it.

So:
new_2Darray = Array2D(10,5)
new_2Darray[3,1,4,0] = "H"
printing out the array gives:
X X X X X X X X X X
X X X H X X X X X X
X X X X X X X X X X
X X X X X X X X X X
X X X X X X X X X X

column = 3
row = 1
cell index = 4 (5th element of the array sitting in that cell of the 2D
array)
direction = 0

I'm not sure how efficient this will be, or whether it will be
restrictive in using some of the default Ruby methods, but it's
certainly makes my code very neat.

Thx again for everyone's help.

···

--
Posted via http://www.ruby-forum.com/\.

That's what I thought, too.

And now I can't duplicate the effect.

OK, I'm very puzzled. {shrug}

···

On Jul 18, 2010, at 1:43 , Martin DeMello wrote:

That shouldn't have happened - the block passed to Array.new should
create a new array every time it's called.