views:

1666

answers:

5

Say I have a rectangular string array (stringarray[rows, columns] - not a jagged array). What's the best way to extract a one-dimensional array from this (either a single row or a single column)? I can do this with a for loop, of course, but I'm hoping .NET has a more elegant way built in.

Bonus points for converting the extracted string array to an object array.

+1  A: 

LINQ is the answer

static object[] GetColumn(string[][] source, int col) {
    return source.Iterate().Select(x => source[x.Index][col]).Cast<object>().ToArray();
}
static object[] GetRow(string[][] source, int row) {
    return source.Skip(row).First().Cast<object>().ToArray();
}
public class Pair<T> {
    public int Index;
    public T Value;
    public Pair(int i, T v) {
        Index = i;
        Value = v;
    }
}
static IEnumerable<Pair<T>> Iterate<T>(this IEnumerable<T> source) {
    int index = 0;
    foreach (var cur in source) {
        yield return new Pair<T>(index, cur);
        index++;
    }
}
JaredPar
I don't think that's doing what's been asked for ("either a single row or a single column").
Jon Skeet
Wouldn't LINQ use a loop internally?
Rohit
Thanks for pointing that out Jon. I read right past that part of the answer.
JaredPar
+7  A: 

You can cast a string array to an object array trivially - going the other way doesn't work. The actual extraction has to use a for loop though, as far as I can see: Array.Copy requires the source and target ranks to be the same, and Buffer.BlockCopy only works for value type arrays. It does seem odd though...

You can use LINQ to extra a row or column in a single statement, although it will be inefficient (as it'll build up a list internally, then have to convert it to an array - if you do it yourself you can preallocate the array to the right size and copy directly).

Copying a row (rowNum is the row to be copied):

object[] row = Enumerable.Range(0, rowLength)
                         .Select(colNum => (object) stringArray[rowNum, colNum])
                         .ToArray();

Copying a column (colNum is the column to be copied):

object[] column = Enumerable.Range(0, columnLength)
                            .Select(rowNum => (object) stringArray[rowNum, colNum])
                            .ToArray();

I'm not sure that this is really any better/simpler than a foreach loop though - particularly if you write an ExtractRow method and an ExtractColumn method and reuse them.

Jon Skeet
Just making sure I'm not reinventing the wheel. This particular task certainly doesn't occur in nature often enough to warrant a built-in function.
MusiGenesis
I don't know - I think there are more obscure things that the framework supports. I'm still slightly surprised that there isn't a row copy (as that should be blittable).
Jon Skeet
+4  A: 

I'd just like to clarify (given the example and what's being asked).

A jagged array is an array of arrays and is declared like so:

string[][] data = new string[3][];
data[0] = new string[] { "0,[0]", "0,[1]", "0,[2]" };
data[1] = new string[] { "1,[0]", "1,[1]", "1,[2]" ];
data[2] = new string[] { "2,[0]", "1,[1]", "1,[2]" };

Versus a rectangular array being defined as a single array that holds multiple dimensions:

string[,] data = new string[3,3];
data[0,0] = "0,0";
data[0,1] = "0,1";
data[0,2] = "0,2";
...etc

Because of this, a jagged array is IQueryable/IEnumerable because you can iterate over it to receive an array at each iteration. Whereas a rectangular array is not IQueryable/IEnumerable because elements are addressed in full dimension (0,0 0,1..etc) so you won't have the ability to use Linq or any predefined functions created for Array in that case.

Though you can iterate over the array once (and achieve what you want) like this:

/// INPUT: rowIndex, OUTPUT: An object[] of data for that row
int colLength = stringArray.GetLength(1);
object[] rowData = new object[colLength];
for (int col = 0; col < colLength; col++) {
    rowData[col] = stringArray[rowIndex, col] as object;
}
return rowData;

/// INPUT: colIndex, OUTPUT: An object[] of data for that column
int rowLength = stringArray.GetLength(0);
object[] colData = new object[rowLength];
for (int row = 0; r < rowLength; row++) {
    colData[row] = stringArray[row, colIndex] as object;
}
return colData;

Hope this helps :)

nyxtom
A: 

Rows can be copied easily using Array.Copy:

        int[][] arDouble = new int[2][];
        arDouble[0] = new int[2];
        arDouble[1] = new int[2];
        arDouble[0][0] = 1;
        arDouble[0][1] = 2;
        arDouble[1][0] = 3;
        arDouble[1][1] = 4;

        int[] arSingle = new int[arDouble[0].Length];

        Array.Copy(arDouble[0], arSingle, arDouble[0].Length);

This will copy the first row into the single Dimension array.

This does not work for two dimesional arrays, as seen in nyxtom's answer above.
Isak Savo
I guess the terminology is "rectangular array", not "two dimensional"
Isak Savo
I edited the question to use "rectangular" instead of "two-dimensional".
MusiGenesis
+1  A: 

For a rectangular array:

string[,] rectArray = new string[3,3] { 
    {"a", "b", "c"}, 
    {"d", "e", "f"}, 
    {"g", "h", "i"} };

var rectResult = rectArray.Cast<object>().Select(s => s).ToArray();

And for a jagged array:

string[][] jaggedArray =  { 
    new string[] {"a", "b", "c", "d"}, 
    new string[] {"e", "f"}, 
    new string[] {"g", "h", "i"} };

var jaggedResult = jaggedArray.SelectMany(s => s).Cast<object>().ToArray();
Cameron MacFarland