tags:

views:

11782

answers:

8

I have a two-dimensional array (of Strings) which make up my data table (of rows and columns). I want to sort this array by any column. I tried to find an algorithm for doing this in C#, but have not been successful.

Any help is appreciated.

+3  A: 

Here is an article from Jim Mischel at InformIt that handles sorting for both rectangular and jagged multi-dimensional arrays.

Doug L.
That example doesn't actually sort the array; the LINQ will produce a sorted sequence, but only if you capture the result... it doesn't sort the existing array. This could just be: string[] names = {"Smith","Snyder","Baker","Jonson","Ballmer"}; Array.Sort(names);
Marc Gravell
I can see what you're saying - I'll remove the flawed example, but leave in the link to the sorting article. PS - thank you for telling me the reason for the down vote. You don't see that often but it truely IS constructive!
Doug L.
And I've removed the down-vote since you've fixed it ;-p
Marc Gravell
+4  A: 

Load your two-dimensional string array into an actual DataTable (System.Data.DataTable), and then use the DataTable object's Select() method to generate a sorted array of DataRow objects (or use a DataView for a similar effect).

// assumes stringdata[row, col] is your 2D string array
DataTable dt = new DataTable();
// assumes first row contains column names:
for (int col = 0; col < stringdata.GetLength(1); col++)
{
    dt.Columns.Add(stringdata[0, col]);
}
// load data from string array to data table:
for (rowindex = 1; rowindex < stringdata.GetLength(0); rowindex++)
{
    DataRow row = dt.NewRow();
    for (int col = 0; col < stringdata.GetLength(1); col++)
    {
        row[col] = stringdata[rowindex, col];
    }
    dt.Rows.Add(row);
}
// sort by third column:
DataRow[] sortedrows = dt.Select("", "3");
// sort by column name, descending:
sortedrows = dt.Select("", "COLUMN3 DESC");

You could also write your own method to sort a two-dimensional array. Both approaches would be useful learning experiences, but the DataTable approach would get you started on learning a better way of handling tables of data in a C# application.

MusiGenesis
That sounds interesting, can you post or link to some code examples please.
Jack
Done. It might have a bug somewhere - I wrote it in notepad.
MusiGenesis
Amazed you wrote that in notepad - at any rate, it worked very well. Thank you.
Jack
A: 

So your array is structured like this (I'm gonna talk in pseudocode because my C#-fu is weak, but I hope you get the gist of what I'm saying)

string values[rows][columns]

So value[1][3] is the value at row 1, column 3.

You want to sort by column, so the problem is that your array is off by 90 degrees.

As a first cut, could you just rotate it?

std::string values_by_column[columns][rows];

for (int i = 0; i < rows; i++)
  for (int j = 0; j < columns; j++)
    values_by_column[column][row] = values[row][column]

sort_array(values_by_column[column])

for (int i = 0; i < rows; i++)
  for (int j = 0; j < columns; j++)
    values[row][column] = values_by_column[column][row]

If you know you only want to sort one column at a time, you could optimize this a lot by just extracting the data you want to sort:

  string values_to_sort[rows]
  for (int i = 0; i < rows; i++)
    values_to_sort[i] = values[i][column_to_sort]

  sort_array(values_to_sort)

  for (int i = 0; i < rows; i++)
    values[i][column_to_sort] = values_to_sort[i]

In C++ you could play tricks with how to calculate offsets into the array (since you could treat your two-dimensional array as a one-d array) but I'm not sure how to do that in c#.

Moishe
+1  A: 
David Hall
+7  A: 

Can I check - do you mean a rectangular array ([,])or a jagged array ([][])?

It is quite easy to sort a jagged array; I have a discussion on that here. Obviously in this case the Comparison<T> would involve a column instead of sorting by ordinal - but very similar.

Sorting a rectangular array is trickier... I'd probably be tempted to copy the data out into either a rectangular array or a List<T[]>, and sort there, then copy back.

Here's an example using a jagged array:

static void Main()
{  // could just as easily be string...
    int[][] data = new int[][] { 
        new int[] {1,2,3}, 
        new int[] {2,3,4}, 
        new int[] {2,4,1} 
    }; 
    Sort<int>(data, 2); 
} 
private static void Sort<T>(T[][] data, int col) 
{ 
    Comparer<T> comparer = Comparer<T>.Default;
    Array.Sort<T[]>(data, (x,y) => comparer.Compare(x[col],y[col])); 
}

For working with a rectangular array... well, here is some code to swap between the two on the fly...

static T[][] ToJagged<T>(this T[,] array) {
    int height = array.GetLength(0), width = array.GetLength(1);
    T[][] jagged = new T[height][];

    for (int i = 0; i < height; i++)
    {
        T[] row = new T[width];
        for (int j = 0; j < width; j++)
        {
            row[j] = array[i, j];
        }
        jagged[i] = row;
    }
    return jagged;
}
static T[,] ToRectangular<T>(this T[][] array)
{
    int height = array.Length, width = array[0].Length;
    T[,] rect = new T[height, width];
    for (int i = 0; i < height; i++)
    {
        T[] row = array[i];
        for (int j = 0; j < width; j++)
        {
            rect[i, j] = row[j];
        }
    }
    return rect;
}
// fill an existing rectangular array from a jagged array
static void WriteRows<T>(this T[,] array, params T[][] rows)
{
    for (int i = 0; i < rows.Length; i++)
    {
        T[] row = rows[i];
        for (int j = 0; j < row.Length; j++)
        {
            array[i, j] = row[j];
        }
    }
}
Marc Gravell
It is rectangular.
Jack
OK; I've added some tricks ;-p
Marc Gravell
A: 

Try this out. The basic strategy is to sort the particular column independently and remember the original row of the entry. The rest of the code will cycle through the sorted column data and swap out the rows in the array. The tricky part is remembing to update the original column as the swap portion will effectively alter the original column.


        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++;
            }
        }
        static void Sort2d(string[][] source, IComparer comp, int col) {
            var colValues = source.Iterate()
                .Select(x => new Pair<string>(x.Index,source[x.Index][col])).ToList();
            colValues.Sort((l,r) => comp.Compare(l.Value, r.Value));
            var temp = new string[source[0].Length];
            var rest = colValues.Iterate();
            while ( rest.Any() ) {
                var pair = rest.First();
                var cur = pair.Value;
                var i = pair.Index;
                if (i == cur.Index ) {
                    rest = rest.Skip(1);
                    continue;
                }

                Array.Copy(source[i], temp, temp.Length);
                Array.Copy(source[cur.Index], source[i], temp.Length);
                Array.Copy(temp, source[cur.Index], temp.Length);
                rest = rest.Skip(1);
                rest.Where(x => x.Value.Index == i).First().Value.Index = cur.Index;
            }
        }

        public static void Test1() {
            var source = new string[][] 
            {
                new string[]{ "foo", "bar", "4" },
                new string[] { "jack", "dog", "1" },
                new string[]{ "boy", "ball", "2" },
                new string[]{ "yellow", "green", "3" }
            };
            Sort2d(source, StringComparer.Ordinal, 2);
        }
JaredPar
A: 

If you could get the data as a generic tuple when you read it in or retrieved it, it would be a lot easier; then you would just have to write a Sort function that compares the desired column of the tuple, and you have a single dimension array of tuples.

Raindog
A: 

I like the DataTable approach proposed by MusiGenesis above. The nice thing about it is that you can sort by any valid SQL 'order by' string that uses column names, e.g. "x, y desc, z" for 'order by x, y desc, z'. (FWIW, I could not get it to work using column ordinals, e.g. "3,2,1 " for 'order by 3,2,1') I used only integers, but clearly you could add mixed type data into the DataTable and sort it any which way.

In the example below, I first loaded some unsorted integer data into a tblToBeSorted in Sandbox (not shown). With the table and its data already existing, I load it (unsorted) into a 2D integer array, then to a DataTable. The array of DataRows is the sorted version of DataTable. The example is a little odd in that I load my array from the DB and could have sorted it then, but I just wanted to get an unsorted array into C# to use with the DataTable object.


    static void Main(string[] args)
    {
        SqlConnection cnnX = new SqlConnection("Data Source=r90jroughgarden\\;Initial Catalog=Sandbox;Integrated Security=True");
        SqlCommand cmdX = new SqlCommand("select * from tblToBeSorted", cnnX);
        cmdX.CommandType = CommandType.Text;
        SqlDataReader rdrX = null;
        if (cnnX.State == ConnectionState.Closed) cnnX.Open();

        int[,] aintSortingArray = new int[100, 4];     //i, elementid, planid, timeid

        try
        {
            //Load unsorted table data from DB to array
            rdrX = cmdX.ExecuteReader();
            if (!rdrX.HasRows) return;

            int i = -1;
            while (rdrX.Read() && i < 100)
            {
                i++;
                aintSortingArray[i, 0] = rdrX.GetInt32(0);
                aintSortingArray[i, 1] = rdrX.GetInt32(1);
                aintSortingArray[i, 2] = rdrX.GetInt32(2);
                aintSortingArray[i, 3] = rdrX.GetInt32(3);
            }
            rdrX.Close();

            DataTable dtblX = new DataTable();
            dtblX.Columns.Add("ChangeID");
            dtblX.Columns.Add("ElementID");
            dtblX.Columns.Add("PlanID");
            dtblX.Columns.Add("TimeID");
            for (int j = 0; j < i; j++)
            {
                DataRow drowX = dtblX.NewRow();
                for (int k = 0; k < 4; k++)
                {
                    drowX[k] = aintSortingArray[j, k];
                }
                dtblX.Rows.Add(drowX);
            }

            DataRow[] adrowX = dtblX.Select("", "ElementID, PlanID, TimeID");
            adrowX = dtblX.Select("", "ElementID desc, PlanID asc, TimeID desc");

        }
        catch (Exception ex)
        {
            string strErrMsg = ex.Message;
        }
        finally
        {
            if (cnnX.State == ConnectionState.Open) cnnX.Close();
        }
    }
Jeffrey Roughgarden