Referencing [,] element as [ ]?

  • Thread starter Thread starter David Veeneman
  • Start date Start date
D

David Veeneman

I have a two-dimensional array with 6 x 7 elements. I'd like to be able to
reference the elements sequentially, as if the rows were laid end-to-end. I
know the Array.Length property will give me the length of the array as 42,
but I'd like to be able to reference specific elements. For example, I'd
like to be able to reference foo[3,2] as foo[24].

Is there any way to do this natively in C#? Right now I'm using a conversion
function that takes an index and returns a cell reference. I'd like to
eliminate the function, if I can. Thanks in advance.
 
Something like this?

foo[Math.Floor(24/7),24%7] // foo[24]

In C you could fo what you say, but they were just downright nasty memory
overruns that happened to work...
 
David said:
I have a two-dimensional array with 6 x 7 elements. I'd like to be able to
reference the elements sequentially, as if the rows were laid end-to-end. I
know the Array.Length property will give me the length of the array as 42,
but I'd like to be able to reference specific elements. For example, I'd
like to be able to reference foo[3,2] as foo[24].

Is there any way to do this natively in C#? Right now I'm using a conversion
function that takes an index and returns a cell reference. I'd like to
eliminate the function, if I can. Thanks in advance.

I don't think so. Your best strategy may be to allocate your array as
a 43 element array. That way, the code that needs to deal with rows
and columns can call your product and sum methods, while the code that
can deal with vectors goes straight to the vector.
 
David said:
I have a two-dimensional array with 6 x 7 elements. I'd like to be able to
reference the elements sequentially, as if the rows were laid end-to-end. I
know the Array.Length property will give me the length of the array as 42,
but I'd like to be able to reference specific elements. For example, I'd
like to be able to reference foo[3,2] as foo[24].

Is there any way to do this natively in C#? Right now I'm using a conversion
function that takes an index and returns a cell reference. I'd like to
eliminate the function, if I can. Thanks in advance.

Just in case anyone else was investigating this line of enquiry - using
the IList indexer implementation for arrays doesn't work. For instance:

using System;
using System.Collections;

class Test
{
static void Main()
{
string[,] array = new string[10,20];
array[0,5] = "Hello";
array[3,4] = "There";

IList list = (IList)array;

Console.WriteLine (list[5]);
Console.WriteLine (list[64]);
}
}

compiles but gives the following result:
Unhandled Exception: System.ArgumentException: Array was not a
one-dimensional array.
at System.Array.GetValue(Int32 index)
at System.Array.System.Collections.IList.get_Item(Int32 index)
at Test.Main()

(Using 2.0 and generics doesn't help either as far as I can see.)

Jon
 
I have a two-dimensional array with 6 x 7 elements. I'd like to be able to
reference the elements sequentially, as if the rows were laid end-to-end.

That's not directly possible with .NET arrays, at least not in C#.
However, you could access the array with foreach -- the iterator will
loop through all elements sequentially, but you won't know the current
overall index unless you keep track of it yourself.
 
Christoph said:
That's not directly possible with .NET arrays, at least not in C#.
However, you could access the array with foreach -- the iterator will
loop through all elements sequentially, but you won't know the current
overall index unless you keep track of it yourself.

In fact, it's not just C#. As far as I can tell, IL doesn't have any
direct instructions for accessing an array element unless the array is
actually a vector (in CLI terminology) - a single-dimensional array
with a lower bound of 0.
From the CLI spec (first edition, admittedly), section 4.7 of partition
III:

<quote>
The ldelem instruction loads the value of the element with index
'index' (of type int32 or native int) in the zero-based, one
dimensional array 'array' and places it on the top of the stack.
....
For one-dimesional arrays that aren't zero-based, and for
multi-dimensional arrays, the array class provides a Get method.
</quote>

Of course, it wouldn't be hard to write an ArrayUtil class which took
an arbitrary Array and an index, then use GetUpperBounds etc to access
the relevant element. It wouldn't be very efficient though...

Jon
 
I was afraid that was the case. For the benefit of anyone else researching
the question, here is the code I use to get a cell reference from an index
number. It assumes a 6 x 7 array:


private CellRef GetCellRefFromIndex(int index)
{
// Get the row number for the index passed in
int row = index / 7;

// Get the column number for the index passed in
int column = (index % 7) - 1;
if (column == -1) column = 6;

// Set return value
return new CellRef(row, column);
}


The code returns a CellRef, which is a custom struct:


private struct CellRef
{
public int Row;
public int Column;

public CellRef(int row, int column)
{
Row = row;
Column = column;
}
}
 
David said:
// Get the column number for the index passed in
int column = (index % 7) - 1;
if (column == -1) column = 6;

Looks like 1 <= index <= 43. Fwiw,

int column = (index - 1) % 7;

should be more efficient than your code, with its conditional
statement.
 
I have a two-dimensional array with 6 x 7 elements. I'd like to be able to
reference the elements sequentially, as if the rows were laid end-to-end. I
know the Array.Length property will give me the length of the array as 42,
but I'd like to be able to reference specific elements. For example, I'd
like to be able to reference foo[3,2] as foo[24].

Is there any way to do this natively in C#? Right now I'm using a conversion
function that takes an index and returns a cell reference. I'd like to
eliminate the function, if I can. Thanks in advance.

You could wrap the underlying array in a class and provide two
separate indexers: [] and [,]. For reasons of speed I have used one
dimension rather than two for the underlying array. YMMV.

public class CellRefArray {
private int m_rows;
private int m_cols;
private CellRef[] m_CRArray;

// Constructor
public CellRefArray(int rows, int cols) {
m_rows = rows;
m_cols = cols;
m_CRArray = new CellRef[m_rows * m_cols];
}

// Indexer[]
public CellRef this[int i] {
get { return m_CRArray; }
set { m_CRArray = value; }
}

// Indexer[,]
public CellRef this[int r, int c] {
get { return m_CRArray[r * m_cols + c]; }
set { m_CRArray[r * m_cols + c] = value; }
}
}

You may want to add some range checking to the two indexers.

Depending on precisely what you want you might also want to add a
second constructor that takes an array[,] as parameter or a property
that returns an array[,].

rossum
 
Jon Shemitz said:
Looks like 1 <= index <= 43. Fwiw,

int column = (index - 1) % 7;

should be more efficient than your code, with its conditional
statement.

However, it's not equivalent. The index 0 would give column=-1 with
your code, but column=6 for David's. To make it equivalent, however
(for non-negative values of "index") you could use (index+6)%7 instead.

Alternatively, it would be worth trying Math.DivRem. I haven't
benchmarked it, so I've no idea whether it would be faster or not - but
if efficiency is critical, it's worth a try.

I'm not sure why David's subtracting one in the first place, however...
I don't think he should be doing so.
 
Jon Skeet said:
However, it's not equivalent. The index 0 would give column=-1 with
your code, but column=6 for David's. To make it equivalent, however
(for non-negative values of "index") you could use (index+6)%7 instead.

Doh, sorry, missed the first line of your reply. Assuming a positive
value for index, your code is equivalent. Maybe I should just go to bed
now...

However, I still think there's a bug in David's code. Here's the
index/row/column values:

1/0/0
2/0/1
3/0/2
4/0/3
5/0/4
6/0/5
7/1/6 <-- Bug!
8/1/0
9/1/1
10/1/2
11/1/3
12/1/4
13/1/5
14/2/6 <-- Bug!

(etc)

Basically, if the index is to be treated as 1-based, it should be
treated that way consistently for both the row and the column.

(Personally I'd suggest just using a zero-based index to start with,
but there we go.)
 
Jon Skeet said:
However, it's not equivalent. The index 0 would give column=-1 with
your code, but column=6 for David's.

Yes, this is why I assume he's using a 1-based index.
To make it equivalent, however
(for non-negative values of "index") you could use (index+6)%7 instead.

True.
 
Why not just create your own collection class and implement the indexers
to do what you want?



I have a two-dimensional array with 6 x 7 elements. I'd like to be able to
reference the elements sequentially, as if the rows were laid end-to-end. I
know the Array.Length property will give me the length of the array as 42,
but I'd like to be able to reference specific elements. For example, I'd
like to be able to reference foo[3,2] as foo[24].

Is there any way to do this natively in C#? Right now I'm using a conversion
function that takes an index and returns a cell reference. I'd like to
eliminate the function, if I can. Thanks in advance.

You could wrap the underlying array in a class and provide two
separate indexers: [] and [,]. For reasons of speed I have used one
dimension rather than two for the underlying array. YMMV.

public class CellRefArray {
private int m_rows;
private int m_cols;
private CellRef[] m_CRArray;

// Constructor
public CellRefArray(int rows, int cols) {
m_rows = rows;
m_cols = cols;
m_CRArray = new CellRef[m_rows * m_cols];
}

// Indexer[]
public CellRef this[int i] {
get { return m_CRArray; }
set { m_CRArray = value; }
}

// Indexer[,]
public CellRef this[int r, int c] {
get { return m_CRArray[r * m_cols + c]; }
set { m_CRArray[r * m_cols + c] = value; }
}
}

You may want to add some range checking to the two indexers.

Depending on precisely what you want you might also want to add a
second constructor that takes an array[,] as parameter or a property
that returns an array[,].

rossum
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top