【问题标题】:exclude items from array through loop c#通过循环c#从数组中排除项目
【发布时间】:2021-02-02 07:43:18
【问题描述】:

我正在尝试从数组中删除项目以过滤掉包含某些特定单词的项目,我正在越界异常,我想不出如何解决它...请帮助!

string[] files = {
                "image4.png",
                "copy.psd",
                "image3.jpg",
                "image1.png",
                "image2.png",
            };
            string numToRemove = "";
            string[] namesArray = Console.ReadLine().Split(',');
            int num = 0;
            foreach(string t in files){
                Console.WriteLine(t);
                foreach(string s in namesArray){ 
                    if(files[num].Contains(s)){
                        numToRemove = s;
                        Console.WriteLine("exist");
                        files = files.Where(val => val != numToRemove).ToArray();
                    }
                }
                num++;
            };

编辑: 感谢大家的快速解答和解决方案

【问题讨论】:

  • 你不能使用列表吗?它会让你的生活更轻松,无论如何,有很多数组操作可以帮助你:dotnetperls.com/array-resize
  • files 数组的 foreach 中重新分配 files 数组是一个可怕的计划,肯定会在你的脸上炸毁。如果你想删除 sans-LINQ,正常的过程是向后迭代(正常的 for 循环),这样你就可以删除而不是弄乱你的索引。

标签: c# loops indexoutofboundsexception


【解决方案1】:

我认为您正在寻找这样的东西:

files = files.Except(namesArray).ToArray();

命名空间要求:System.Linq;

【讨论】:

  • 虽然可以替代他们的 linq 尝试,但不能解决后续循环中索引超出范围的问题。
  • @DRapp,但它消除了对 OP 发布的完整代码的需要(可能除了您在答案中添加的 Trim
【解决方案2】:

公平地说,两位回答者都对您的原始问题给出了很好的意见。 DRapp 让您了解它发生的原因,Tân 为您提供替代解决方案。

我认为这两种解决方案都有其优点,但我不明白为什么你会想要像 DRapp 的示例那样一遍又一遍地使用 for 循环和重新创建同一个数组。

假设您不知道 Tân 在他的回答中描述的替代方案,可能在您的代码中最简单的方法是提取功能并对其进行概括,使其灵活(并且可重用,但这并不是真正的点)。

为此,您只需创建一个返回 IEnumerable<string> 的新方法。

假设您提取了从数据集中删除现有条目的代码,您可以编写如下内容:

private static IEnumerable<string> GetWithout(IEnumerable<string> entries, IEnumerable<string> toExclude) {
    var excludeSet = new HashSet<string>( toExclude, StringComparer.OrdinalIgnoreCase );
    foreach (var entry in entries) {
        if (excludeSet.Contains( entry )) {
            continue;
        }
        yield return entry;
    }
}

然后您可以稍后调用此代码,如下所示:

public static void Main()
{
    var files = new string[] {
            "image4.png",
            "copy.psd",
            "image3.jpg",
            "image1.png",
            "image2.png",
        };
    var input = "copy.psd, more, test, Image4.png";
    
    var filesWithoutInput = GetWithout( files, input.Split(',').Select( item => item.Trim() ) );
    Console.WriteLine( string.Join(", ", filesWithoutInput ) );
}

我发现 Tân 提供的代码最容易阅读,它简洁并支持不同的 IEqualityComparer 作为第二个参数。它也是框架的一部分,并且是一种扩展方法,您基本上可以将它用于所有类型,并且只要您将 System.Linq 命名空间添加到您的 usings 中,您就可以在任何地方使用它。

我上面分享的代码没有那个,它不允许你选择比较器,所以它不太灵活。如果您想要区分大小写的比较,则必须更改代码。如果你想在其他地方重用代码,你会遇到同样的问题。

你当然可以像下面这样重写它

internal static class EnumerableExtensions {
    public static IEnumerable<T> GetWithout<T>(this IEnumerable<T> entries, IEnumerable<T> toExclude, IEqualityComparer<T> comparer = default(IEqualityComparer<T>)) {
        var excludeSet = new HashSet<T>( toExclude, comparer );
        foreach (var entry in entries) {
            if (excludeSet.Contains( entry )) {
                continue;
            }
            yield return entry;
        }
    }
}

这将具有 System.Linq.Enumerable.Except 方法的所有优点,但在这种情况下,我不明白您为什么要这样做。

在我的代码示例和 Tân 的回答中,如果您的 files 数组包含多次相同的名称,您可能会得到重复的条目,但为此您可以将其更改为 @987654332 @或files.Distinct().Except( namesArray )

【讨论】:

  • 我从你的问题中评论了我的回答。我确实喜欢你的,但可以枚举只返回允许的内容。不错的选择。
【解决方案3】:

您的问题不是 linq .ToArray() 调用,而是文件列表的 foreach() 循环。当循环开始时,它会将内部计数器初始化为文件数组 (5) 的原始大小,在这种情况下。当您通过并成功在 files 数组中找到匹配项时,您会动态地将其大小重新调整为 4。所以现在,循环仍在循环遍历所有 5 个原始计数,这就是您崩溃的地方。

相反,使用 for() 循环文件循环,计数器从最高长度开始并返回。因此,如果您在列表中的第 3 项上找到匹配项,则在调整 files 数组大小后,您的下一个周期将是 2。当您工作到零时,有效的索引 #2 仍然存在。

我遇到的另一件事是有人在每个逗号后放置空格的测试。您的数组(如本例中我硬编码一个字符串)将创建您的字符串,例如

namesArray[0] = "copy.psd"
namesArray[1] = " more"
namesArray[2] = " test"
namesArray[3] = " image4.png"

注意到拆分中字符串的前导空格吗?这将给出假阴性并跳过(在本例中)image4.png 被删除。

我更新了一个函数以显示您正在尝试的工作实例,并在字符串“split()”后预先修剪字符串。

    private void testArray()
    {
        string[] files = {
            "image4.png",
            "copy.psd",
            "image3.jpg",
            "image1.png",
            "image2.png",
        };

        string[] namesArray = "copy.psd, more, test, image4.png".Split(',');
        // pre-trim each string for proper test when comparing strings in arrays
        for (var na = 0; na < namesArray.Length; na++)
            namesArray[na] = namesArray[na].Trim();


        // Now, look for each files to the namesArray
        for ( var outerNum = files.Length -1; outerNum >= 0; outerNum-- )
        {
            var curFileName = files[outerNum];
            Console.WriteLine(curFileName);

            if( namesArray.Contains(curFileName))
            {
                Console.WriteLine(curFileName + " exist");
                files = files.Where(val => val != curFileName).ToArray();
            }
        };

        Console.Write(files.ToString());
    }

【讨论】:

  • 我尽我所能给出一个替代答案。我认为你的解决了 OP 的直接问题,但它有一个 for 循环,在 for 循环中它使用 Contains, Where + ToArray。我知道原始代码有类似的设置,我只是认为代码不容易阅读,有点冗长。该框架提供了一种方法来做到这一点,如果他需要它是一个数组,他可以对结果执行 ToArray。如果他想知道两个列表中都有哪些文件,可以使用Intersect
  • 感谢您的回答,这确实有助于我理解为什么它不起作用,但是如果包含某些扩展名,我仍然需要排除所有项目。我没有在帖子中提到这一点,但再次感谢您的回答。
  • @user78430,这确实会删除列表中的那些,并减少生成的文件数组。我刚刚从列表的末尾开始工作,以纠正您的索引超出范围的问题。结果还是一样。
  • @user78430,同样的上下文也适用。除了“包含(curFileName)”部分,您可能需要应用 Path.GetExtension(curFileName) == 您正在处理的 fileNames 数组元素。例如:您想排除所有“.pdf”、“.png”等。希望此评论可以帮助您。
猜你喜欢
  • 2021-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-12
  • 2012-05-30
相关资源
最近更新 更多