【问题标题】:Fast way for String.Except like List.ExceptString.Except 的快速方法,例如 List.Except
【发布时间】:2019-06-05 11:00:11
【问题描述】:

像新的 .NET 6、7 等一样,我们有一个用于列表的除外类。

List<int> A = new List<int>();
List<int> B = new List<int>();
List<int> C = A.Except(B).ToList();

我的问题是,如何最好地处理同一类的字符串版本:

string A = "<div><p>One</p><p>Two</p></div>";
string B = "<div><p>One</p><p>Two</p><p>Three</p></div>";
string C = A.Except(B).ToString();

得到结果 = &lt;p&gt;Three&lt;/p&gt;

相反,我得到:

System.Linq.Enumerable+<ExceptIterator>d__73`1[System.Char]

我做错了什么?

编辑:

只用最大的字符串排除最小的字符串,颠倒数组顺序:

string C = B.Except(A);

并使用:Nick's new string(C.ToArray()); 给我:

hr

使用反向后略有出乎意料的结果。

【问题讨论】:

  • 什么是 .NET 6 和 7?我知道最新的 .NET Framework 4.7 和 .NET Core 2.2。
  • 我想知道哪种算法会从两个字符串中生成&lt;p&gt;Three&lt;/p&gt;。您需要首先考虑这一点,也许用铅笔和纸。
  • 看来我们有一个XY problem 有这个问题。请告诉我们您实际上想要达到什么目标?
  • 如果你想获得类似 EXCEPT 的效果,你必须将你的字符串转换为子字符串列表,然后应用 EXCEPT。因此,您需要将 HTML 片段解析为 "&lt;p&gt;One&lt;/p&gt;""&lt;p&gt;Two&lt;/p&gt;" 等列表。 Guillermo Gutiérrez 为您提供了基础知识。
  • Ciprian Vilcan 很好地解释了为什么您的原始代码不起作用。

标签: c# string except


【解决方案1】:

您的解决方案存在两个问题。

ToString() 的行为

在 IEnumerable 上执行 .ToString() 时,它总是会打印出类型。这是因为 IEnumerable 不会覆盖 ToString() 的行为。有关这方面的更多信息,请参阅ToString
如果您想将 IEnumerable(Except 的返回类型)转换为字符串,则必须这样做

var C = new string(A.Except(B));


A.Except(B) 的行为

Except 方法并不像您想象的那样工作。

以以下代码为例:

var a = new List<int> { 1, 2, 3 };
var b = new List<int> { 2, 3, 4 };
var c = a.Except(b);

结果将是 { 1 }。该方法的有效作用是返回 a 中存在但 b 中不存在的所有 int 的新枚举。

现在,字符串只是字符的枚举——更准确地说,你的

var A = "<div><p>One</p><p>Two</p></div>";

从LINQ的角度来看,相当于

var A = new List<char> { '<', 'd', 'i', 'v', '>', ..., '<', '/', 'd', 'i', 'v', '>' };

B也是如此。

因此,当您执行 A.Except(B) 时,LINQ 实际上会遍历每个字符,看看它是否可以在 B 中找到它。如果可以,它不会最终出现在结果集中。现在,由于 A 中的所有字符也存在于 B 中,因此您将得到一个空字符串。要查看实际情况,稍微修​​改 A 使​​其包含 B 中不存在的字符:

string A = "<div><p>One</p><p>Two</p></div>ApplePie";

如果你现在这样做

string A = "<div><p>One</p><p>Two</p></div>ApplePie";
string B = "<div><p>One</p><p>Two</p><p>Three</p></div>";
string C = new string(A.Except(B).ToArray());

你会得到“AlP”。

解决方案

在我看来,执行 except 的最佳方法是解析字符串,将它们转换为对象,然后执行 except。没有内置算法能够判断您的字符串实际上是结构化的以及如何区分它们。作为一个可行的解决方案,使用HtmlAgilityPack (a nuget package)

var docB = new HtmlDocument();
docB.LoadHtml(B);

var docA = new HtmlDocument();
docA.LoadHtml(A);
var nodes = docB.DocumentNode.FirstChild.Descendants("p").Select(node => node.InnerHtml)
    .Except(docA.DocumentNode.FirstChild.ChildNodes.Select(node => node.InnerHtml));
// take note that we are actually doing whatIsInB.Except(whatIsInA), since doing the reverse would result in nothing. There is no &lt;p&gt; in A that is not also present in B

var result = string.Join(Environment.NewLine, nodes); // will resut in "Three"
var otherResult = $"<p>{result}</p>"; // "<p>Three</p>"

我会让你做一个更通用的方法:)
但这个想法是,如果你想按照你期望的方式工作,你必须要求它使用字符串,而不是字符。

是否按照其他解决方案的建议,使用 HtmlAgilityPack 或 Regex 进行提取字符串组件(本示例中的

元素)所需的解析,完全取决于您。

【讨论】:

  • 这在信息方面是好的和完整的,但它并没有提出解决方案。
  • 谢谢 Ciprian Vilcan,感谢您在这里的努力!
【解决方案2】:

当你使用 except() 扩展方法时,返回类型是一个字符列表。

Documentation

此外,A.Excepts(B) 永远不会产生您想要的,因为它将字符串转换为 char 数组。因此,它将从 A 中删除 B 中存在的每个字符。

你需要考虑一个不同的算法来做到这一点。

【讨论】:

    【解决方案3】:

    你想要的不是Except,因为它是一个称为set difference or relative complement 的集合操作,在其中你说你想要一个集合中不存在于另一个集合中的元素。

    您可以使用regular expressions groups 来实现您期望的结果:

    using System;
    using System.Text.RegularExpressions;
    
    class Program
    {
        static void Main()
        {
            // Input string.
            string input = "<div><p>One</p><p>Two</p><p>Three</p></div>";
    
            // Use named group in regular expression.
            Regex expression = new Regex(@"^<div><p>One</p><p>Two</p>(?<middle>[<>/\w]+)</div>$");
    
            // See if we matched.
            Match match = expression.Match(input);
            if (match.Success)
            {
                // Get group by name.
                string result = match.Groups["middle"].Value;
                Console.WriteLine("Middle: {0}", result);
            }
    
            // Done
            Console.ReadLine();
        }
    }
    

    使用正则表达式^&lt;div&gt;&lt;p&gt;One&lt;/p&gt;&lt;p&gt;Two&lt;/p&gt;(?&lt;middle&gt;[&lt;&gt;/\w]+)&lt;/div&gt;$,您说字符串应以&lt;div&gt;&lt;p&gt;One&lt;/p&gt;&lt;p&gt;Two&lt;/p&gt; 开头(^),以&lt;/div&gt; 结尾($),并且介于两者之间的任何内容包含 >/ 或任何字母数字字符 (\w) 超过一次 (+),将被添加到名为 middle 的组中。

    但是,我不建议您使用try to parse HTML with regex...

    【讨论】:

      【解决方案4】:

      使用string C = new string (A.Except(B).ToArray());

      【讨论】:

      • 顺便说一句,这不会产生预期的结果。
      • 一个方便的 char 数组解决方案 - 谢谢!
      猜你喜欢
      • 2016-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-21
      • 1970-01-01
      • 1970-01-01
      • 2015-05-25
      • 2013-11-26
      相关资源
      最近更新 更多