如果项目包含另一个列表中的字符串,则从一个列表中删除项目

Remove items from one list if they contain strings from another list

我正在寻找从一个列表中删除项目的最有效方法,如果它们包含来自另一个列表的字符串。

例如:

B 列表包含:

TomWentFishing
SueStayedHome
JohnGoesToSchool
JimPlaysTennis

列表包含:

GoesToSchool
SueStayed

C 列表应包含:

TomWentFishing
JimPlaysTennis

我用过这段代码,但由于列表非常大,它占用了很多时间:

static void Main(string[] args)
    {
        string[] b = File.ReadAllLines(@"C:\b.txt");
        string[] a = File.ReadAllLines(@"C:\a.txt");

        foreach (string firststring in b)
        {
            bool contains = false;
            foreach (string secondstring in a)
            {
                if (firststring.ToLower().Contains(secondstring.ToLower()))
                {
                    contains = true;
                    break;
                }
            }

            if (contains == false)
            {
                File.AppendAllText(@"C:\c.txt", firststring + Environment.NewLine);
            }


        }

    }

如果问题是由于文件大导致内存使用率高,那么您已经读取了一个文件,但对于另一个文件而不是直接读取内存中的整个文件,您可以使用 FileInputStream 和 BufferedReader 逐行读取行一。这将减少一些内存使用

如果您可以将 a 列表排序为可以支持二进制(或更快)查找的内容,则可以显着 加快速度。

不幸的是,Contains() 搜索使这具有挑战性。但是我们仍然可以做一些事情:

  • 避免将所有 b 加载到 RAM 中。曾经.
  • 另一方面,如果我们预加载到 RAM 一次,查找 a 会更快,并尽可能多地支持对这个副本的查找尽我们所能。
  • 仅将 b 转换为小写一次,而不是对 a 中的每一行再次转换一次。
  • 一次完成所有写入操作会更有效率,而不是 re-opening 输出文件在我们找到它们时追加行。
  • 作为奖励,我们将用更少的代码完成所有这些工作。
static void Main(string[] args)
{
    var b = File.ReadLines(@"C:\b.txt");
    var a = File.ReadLines(@"C:\a.txt").Select(line => line.ToLower()).ToList();

    var result = b.Where(bline => {
       var lowered = bline.ToLower();
       return !a.Any(aline => lowered.Contains(aline));
    });

    File.AppendAllLines(@"C:\c.txt", result);
}

这里有一个非常高效的基于哈希集的实现,它是线性时间复杂度O(n)。这可以避免您为 b.txt 文件中的每一行遍历 a.txt 文件的所有行,从而导致二次时间复杂度 O(n^2).

如果包含 a.txt 文件的所有行的散列集适合内存,则此方法很好。它不适合内存那么你需要使用像 RocksDb 这样的东西。

首先你有这个扩展方法:

public static class EnumerableStringExtensions
{
    public static IEnumerable<string> Minus(
        this IEnumerable<string> minuend, 
        IEnumerable<string> subtrahend, 
        StringComparison comparisonType)
    {
        var subtrahendSet = new HashSet<string>(subtrahend, StringComparer.FromComparison(comparisonType));
        return minuend.Where(x => subtrahendSet.Contains(x) == false);
    }
}

你可以这样使用它:

public class Program
{
    public static IEnumerable<string> EnumerateLines(string filePath)
    {
        using (var reader = File.OpenText(filePath))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }

    static void Main(string[] args)
    {
        var minuend = EnumerateLines("b.txt");
        var sustraend = EnumerateLines("a.txt");
        var difference = minuend.Minus(sustraend, StringComparison.OrdinalIgnoreCase);
        File.WriteAllLines("difference.txt", difference);

    }
}

请注意,通过此实现,您无需将 b.txt 文件中的所有行一次保存在内存中。但是你需要一个哈希集,其中包含 a.txt

中的所有行