4

I have a string array defined in c# as

string[,] options = new string[100,3];

Throughout the code it gets populated with data but not always filled.

So if I have 80 parts of it filled and 20 parts of it not filled. The 20 parts have nulls in them or 60 nulls at the end. Is there an easy way to resize the array so that after filling it the array is the same as

String[,] options = new string[80,3];

It would have to be resized based on the position of the first set of 3 nulls it found.

If this was a jagged array I would have done

options = options.Where(x => x != null).ToArray();
3
  • So if the entire row is null, you remove it? Commented May 7, 2015 at 14:20
  • That is what I am looking for. How would I accomplish? Commented May 7, 2015 at 14:22
  • 3
    I'm thinking you'd be better off with a List<Tuple<string,string,string>> or maybe a list of instances of a custom class with the 3 string values as properties. Commented May 7, 2015 at 14:32

4 Answers 4

7

The method is quite long, because it has to check every row twice...

public static string[,] RemoveEmptyRows(string[,] strs)
{
    int length1 = strs.GetLength(0);
    int length2 = strs.GetLength(1);

    // First we count the non-emtpy rows
    int nonEmpty = 0;

    for (int i = 0; i < length1; i++)
    {
        for (int j = 0; j < length2; j++)
        {
            if (strs[i, j] != null)
            {
                nonEmpty++;
                break;
            }
        }
    }

    // Then we create an array of the right size
    string[,] strs2 = new string[nonEmpty, length2];

    for (int i1 = 0, i2 = 0; i2 < nonEmpty; i1++)
    {
        for (int j = 0; j < length2; j++)
        {
            if (strs[i1, j] != null)
            {
                // If the i1 row is not empty, we copy it
                for (int k = 0; k < length2; k++)
                {
                    strs2[i2, k] = strs[i1, k];
                }

                i2++;
                break;
            }
        }
    }

    return strs2;
}

Use it like:

string[,] options = new string[100, 3];
options[1, 0] = "Foo";
options[3, 1] = "Bar";
options[90, 2] = "fiz";
options = RemoveEmptyRows(options);

As suggested by Alexei, there is another way of doing this:

public static string[,] RemoveEmptyRows2(string[,] strs)
{
    int length1 = strs.GetLength(0);
    int length2 = strs.GetLength(1);

    // First we put somewhere a list of the indexes of the non-emtpy rows
    var nonEmpty = new List<int>();

    for (int i = 0; i < length1; i++)
    {
        for (int j = 0; j < length2; j++)
        {
            if (strs[i, j] != null)
            {
                nonEmpty.Add(i);
                break;
            }
        }
    }

    // Then we create an array of the right size
    string[,] strs2 = new string[nonEmpty.Count, length2];

    // And we copy the rows from strs to strs2, using the nonEmpty
    // list of indexes
    for (int i1 = 0; i1 < nonEmpty.Count; i1++)
    {
        int i2 = nonEmpty[i1];

        for (int j = 0; j < length2; j++)
        {
            strs2[i1, j] = strs[i2, j];
        }
    }

    return strs2;
}

This one, in the tradeoff memory vs time, chooses time. It is probably faster, because it doesn't have to check every row twice, but it uses more memory, because it puts somewhere a list of the non-empty indexes.

Sign up to request clarification or add additional context in comments.

7 Comments

you beat me to this. But from what I can see other than iterating through the array for every section there is no clean way to do this.
@AlexeiLevenkov I could choose between memory and speed. I choose to minimize the use of memory and so minimize speed.
@xanatos why you do comparing if (j < length2) after loop, instead of adding to list before break?
@Grundy Because I copy pasted from the other block of code... We are micro-optimizing here, eh? :-)
@xanatos in other block code you also can move incrementing brefore break :-) methinks it more readable than comparing index after loop
|
0

I went for all rows until you find an row with all null values:

Needs some clean up and will obviously remove non-null rows that occur after the first all null row. The requirement wasn't too clear here

EDIT: Just seen the comment clarifying requirement to remove all null rows - I've tweaked the below to avoid downvotes but a more comprehensive answer is already accepted (and is more efficient) :)

void Main()
{
    string[,] options = new string[100,3];

    options[0,0] = "bleb";
    options[1,1] = "bleb";
    options[2,0] = "bleb";
    options[2,1] = "bleb";
    options[3,2] = "bleb";
    options[4,1] = "bleb";

    string[,] trimmed = TrimNullRows(options);

    Console.WriteLine(trimmed);
}

public string[,] TrimNullRows(string[,] options) 
{
    IList<string[]> nonNullRows = new List<string[]>();
    for (int x = 0; x < options.GetLength(0); x++) 
    {
        bool allNull = true;

        var row = new string[options.GetLength(1)];

        for (int y = 0; y < options.GetLength(1); y++) 
        {
            row[y] = options[x,y];
            allNull &= options[x,y] == null;
        }


        if (!allNull) 
        {
            nonNullRows.Add(row);
        }
    }

    var optionsTrimmed = new string[nonNullRows.Count, options.GetLength(1)];

    for (int i=0;i<nonNullRows.Count;i++)
    {
        for (int j=0;j<options.GetLength(1);j++)
        {
            optionsTrimmed[i, j] = nonNullRows[i][j];
        }
    }


    return optionsTrimmed;
}

Comments

0

You can also get yourself some helpers to convert between jagged and multi-dimensional representations. This is pretty silly, of course, but for arrays as small as the ones you're showing (and also, very sparse arrays), it'll be fine.

void Main()
{
    string[,] options = new string[100,3];

    options[3, 1] = "Hi";
    options[5, 0] = "Dan";

    var results = 
        options
            .JagIt()
            .Where(i => i.Any(j => j != null))
            .UnjagIt();

    results.Dump();
}

static class Extensions
{
    public static IEnumerable<IEnumerable<T>> JagIt<T>(this T[,] array)
    {
        for (var i = 0; i < array.GetLength(0); i++)
            yield return GetRow(array, i);
    }

    public static IEnumerable<T> GetRow<T>(this T[,] array, int rowIndex)
    {
        for (var j = 0; j < array.GetLength(1); j++)
            yield return array[rowIndex, j];
    }

    public static T[,] UnjagIt<T>(this IEnumerable<IEnumerable<T>> jagged)
    {
        var rows = jagged.Count();
        if (rows == 0) return new T[0, 0];

        var columns = jagged.Max(i => i.Count());

        var array = new T[rows, columns];

        var row = 0;
        var column = 0;

        foreach (var r in jagged)
        {
          column = 0;

          foreach (var c in r)
          {
            array[row, column++] = c;
          }

          row++;
        }

        return array;
    }
}

The JagIt method is pretty simple of course - we'll just iterate over the rows, and yield the individual items. This gives us an enumerable of enumerables, which we can use in LINQ quite easily. If desired, you could transform those into arrays, of course (say, Select(i => i.ToArray()).ToArray()).

The UnjagIt method is a bit more talkative, because we need to create the target array with the correct dimensions first. And there's no unyield instruction to simplify that :D

This is pretty inefficient, of course, but that isn't necessarily a problem. You could save yourself some of the iterations by keeping the inner enumerable an array, for example - that will save us having to iterate over all the inner items.

I'm mostly keeping this as the memory-cheap, CPU-intensive alternative to @xanatos' memory-intensive, CPU-cheap (relatively).

Of course, the main bonus is that it can be used to treat any multi-dimensional arrays as jagged arrays, and convert them back again. General solutions usually aren't the most efficient :D

Comments

0

Yet another variant with linq

static string[,] RemoveNotNullRow(string[,] o)
{
    var rowLen = o.GetLength(1);
    var notNullRowIndex = (from oo in o.Cast<string>().Select((x, idx) => new { idx, x })
                group oo.x by oo.idx / rowLen into g
                where g.Any(f => f != null)
                select g.Key).ToArray();

    var res = new string[notNullRowIndex.Length, rowLen];

    for (int i = 0; i < notNullRowIndex.Length; i++)
    {
        Array.Copy(o, notNullRowIndex[i] * rowLen, res, i * rowLen, rowLen);
    }
    return res;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.