从string[,]中移除空值

4

I have a string array defined in c# as

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

在代码中,它总是被数据填充,但并不总是被完全填满。
如果我有80个部分被填充了,而另外20个部分未被填充。这20个部分中可能包含空值,或者在最后连续有60个空值。是否有一种简单的方法来调整数组大小,使其在填充后与原来的数组大小相同?
String[,] options = new string[80,3];

如果这是一个不规则数组,我会根据找到的第一组3个空值的位置来调整其大小。

如果这是一个不规则数组,我就会这样做:

options = options.Where(x => x != null).ToArray();

那么如果整行都是空的,你就删除它了吗? - xanatos
那就是我正在寻找的。我该如何实现呢? - John Wesley Gordon
3
我认为你最好使用一个List<Tuple<string,string,string>>,或者是一个包含三个字符串属性的自定义类实例的列表。 - juharr
4个回答

7

这个方法相对比较冗长,因为它需要检查每一行两次...

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;
}

使用方法如下:

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

正如Alexei所建议的,还有另一种方法可以做到这一点:

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;
}

在时间和内存的权衡中,这个选择了时间。它可能更快,因为它不必两次检查每一行,但它使用了更多的内存,因为它在某个地方放置了一个非空索引列表。

你比我先完成了这个。但是从我的角度来看,除了对每个部分进行数组迭代之外,没有更好的方法来解决这个问题。 - CBRRacer
1
@AlexeiLevenkov 我可以在内存和速度之间做出选择。我选择最小化内存使用,从而最小化速度。 - xanatos
1
@xanatos 为什么你在循环后比较 if (j < length2) 而不是在跳出循环前将其添加到列表中呢? - Grundy
@Grundy 还有其他的 bug 吗?我对那段代码感到非常满意... 我只犯了一个错误就写出来了。现在你却夺走了我的快乐! :-) - xanatos
@xanatos 天空是极限 :-D - Grundy
显示剩余2条评论

0

我遍历所有行,直到找到一个所有值都为null的行:

需要进行一些清理,并且显然会删除在第一个全null行之后出现的非null行。这里要求并不太清楚

编辑:刚看到评论澄清了删除所有空行的要求-我已经进行了微调,以避免获得负面评价,但已经有更全面和更高效的答案被接受了 :)

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;
}

0

使用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;
}

0

你也可以获取一些帮助程序来在锯齿形和多维表示之间进行转换。当然,这很愚蠢,但对于像你展示的这样小的数组(以及非常稀疏的数组),这将是可以接受的。

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;
    }
}

JagIt方法非常简单 - 我们只需迭代行,并yield出单个项。这给我们提供了一个可枚举的可枚举对象,我们可以在LINQ中很容易地使用它们。如果需要,您当然可以将它们转换为数组(例如,Select(i => i.ToArray()).ToArray())。

UnjagIt方法有点啰嗦,因为我们需要首先创建具有正确维度的目标数组。而且没有unyield指令来简化它:D

当然,这非常低效,但这并不一定是问题。例如,通过将内部可枚举对象保持为array,可以节省一些迭代次数 - 这将使我们无需迭代所有内部项。

我主要将其保留为内存便宜、CPU密集型的替代方案,相对而言,@xanatos的方案则是内存密集、CPU便宜。

当然,主要的好处是它可以用于将任何多维数组视为分段数组,并将它们转换回来。通常,通用解决方案并不是最有效的:D


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接