在列表中查找重复项,但有条件

本文关键字:有条件 列表 查找 | 更新日期: 2023-09-27 18:20:09

想要从列表中删除重复项,以便如果我的列表包含:

www.test.com
test.com
mytest.com

我希望最终列表如下所示(仅从前面的副本中选择带有 www 的域(:

www.test.com
mytest.com

我有这个linq,但它似乎忽略了所有前面没有www的域,因为它只选择了www

var result=inputList.Where(x=>x.DomainName.StartsWith("www.")).Distinct();

编辑:

@DanielHilgarth:我只是运行你的代码,它没有产生正确的结果。我有:

test.com 
www.test.com 
test2.com 
www.test2.com 
test3.com 
www.test3.com 
test4.com 

在我的列表中。它返回这个:

test.com
www.test.com
www.test2.com 
www.test3.com 

这是我如何使用您的代码:

var result = lstServerBindings.GroupBy(x => x.DomainName.StartsWith("www.") ? x.DomainName : "www." + x)
                                                .Select(x =>
                                                {
                                                    var domain =
                                                        x.FirstOrDefault(y => y.DomainName.StartsWith("www."));
                                                    if (domain == null)
                                                        return x.First();
                                                    return domain;
                                                });

然后我做一个 foreach 循环来分配给新列表:

foreach (var item in result)
                            {
                                lstUniqueServerBindings.Add(new ServerBindings
                                {
                                    IPAddress = item.IPAddress,
                                    PortNumber = item.PortNumber,
                                    DomainName = item.DomainName
                                });
                            }

在列表中查找重复项,但有条件

我想你想要这样的东西:

var result = domains.GroupBy(x => x.StartsWith("www.") ? x : "www." + x)
                    .Select(x =>
                            {
                                var domain =
                                    x.FirstOrDefault(y => y.StartsWith("www."));
                                if(domain == null)
                                    return x.First();
                                return domain;
                            });

我用这个输入测试了它:

var domains = new List<string>
              {
                  "www.test.com",
                  "test.com",
                  "mytest.com",
                  "abc.com",
                  "www.abc.com"
              };

结果是:

www.test.com
mytest.com
www.abc.com

您的代码应如下所示(请注意第二行末尾的其他.DomainName(:

var result = lstServerBindings.GroupBy(x => x.DomainName.StartsWith("www.") ? 
                                            x.DomainName : "www." + x.DomainName)
                              .Select(x =>
                                      {
                                          var domain =
                                              x.FirstOrDefault(y => 
                                                y.DomainName.StartsWith("www."));
                                          if (domain == null)
                                              return x.First();
                                          return domain;
                                      });

顺便说一句:您可以通过将代码更改为以下内容来保存foreach循环:

var result = lstServerBindings.GroupBy(x => x.DomainName.StartsWith("www.") ? 
                                            x.DomainName : "www." + x.DomainName)
                              .Select(x =>
                                      {
                                          var item =
                                              x.FirstOrDefault(y => 
                                                y.DomainName.StartsWith("www."));
                                          if (item == null)
                                              item = x.First();
                                          return new ServerBindings
                                              {
                                                  IPAddress = item.IPAddress,
                                                  PortNumber = item.PortNumber,
                                                  DomainName = item.DomainName
                                              };
                                      });

这是一个棘手的问题,但有一个相当简单的解决方案:

    public class wwwOrderComparison : IComparer<String>
    {
        public int Compare(string x, string y)
        {
            if(x == null && y == null)
                return 0;
            if(x == null ^ y == null)
                return 0;
            var xWww = x.StartsWith("www");
            var yWww = y.StartsWith("www");
            return (xWww && x == "www." + y) ? -1 : ((yWww && "www." + x == y) ? 1 : 0);
        }
    }
    public class wwwEqualityComparison : IEqualityComparer<String>
    {
        public bool Equals(string x, string y)
        {
            if (x == null && y == null)
                return true;
            if (x == null ^ y == null)
                return false;
            var xWww = x.StartsWith("www");
            var yWww = y.StartsWith("www");
            if (xWww ^ yWww)
                return xWww ? (x == "www." + y) : ("www." + x == y);
            return xWww == yWww;
        }
        public int GetHashCode(string obj)
        {
            return (obj.StartsWith("www.") ? obj : ("www." + obj)).GetHashCode();
        }
    }

这是测试:

        var list = new List<String> {
            "www.test.com", 
            "test.com", 
            "mytest.com", 
            "abc.com", 
            "www.abc.com",
            "zzz.com",
            "www.zzz.com"
        };
        var s = list.OrderBy(t => t, new wwwOrderComparison()).Distinct(new wwwEqualityComparison()).ToList();

这已经通过了我所有的测试。 第二次欢呼:)

编辑:请参阅下面的丹尼尔的回复。我在这个问题上有点太匆忙了。

使用 Select 对元素进行投影,选择/修改某些属性。这听起来可能很复杂,但您需要做的就是:

inputList.Select(x => x.Replace("www.", "")).Distinct()

应该工作!

编辑:一点解释。使用 select,您基本上可以将旧对象映射到新对象,然后选择这些对象进行查询。在上面的情况下,您只选择一个简单的字符串对象,您可以使用以下内容创建一个全新的对象类型:

Select(x => new { Content = x, ContentLength = x.Length, ContentType = x.GetType() })

在这里,您可以根据输入对象的不同属性和方法动态构造一个新对象。选择非常有用且功能强大!

重新定义相等含义的正常 .NET 方法(本质上是您在此处所做的(是实现 IEqualityComparer<T> .

private class IgnoreWWWEqComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        if(ReferenceEquals(x, y))
            return true;
        if(x == null || y == null)
            return false;
        if(x.StartsWith("www."))
        {
            if(y.StartsWith("www."))
                return x.Equals(y);
            return x.Substring(4).Equals(y);
            //the above line can be made faster, but this is a reasonable
            //approach if performance isn't critical
        }
        if(y.StartsWith("www."))
            return x.Equals(y.Substring(4));
        return x.Equals(y);
    }
    public int GetHashCode(string obj)
    {
        if(obj == null)
            return 0;
        if(obj.StartsWith("www."))
            return obj.Substring(4).GetHashCode();
        return obj.GetHashCode();
    }
}

现在Distinct()做你想做的事:

var result=inputList.OrderBy(s => !s.StartsWith("www.")).Distinct(new IgnoreWWWEqComparer());

对于一次性的,您可能会发现只group by删除任何起始www.的字符串并选择每个分组中的第一个更方便,但上面的内容应该更快地丢弃找到的重复项,当然IgnoreWWWEqComparer可以重用。

编辑:

考虑到"www."表单优先的要求,那么以上很好,但是考虑到如果我们有一个非常大的列表来处理它会很糟糕,这确实有点困扰我。如果我们真的欺负性能,我们希望让我们的EqualsGetHashCode变得更好,但可能对几十个人来说排序是可以的,但一段时间后就会开始受到伤害。因此,如果只有一小部分(只是更简单(,我会采取以下方法,但如果它可以非常大:

public static IEnumerable<string> FavourWWWDistinct(IEnumerable<string> src)
{
  Dictionary<string, bool> dict = new Dictionary<string, bool>(new IgnoreWWWEqComparer());
  foreach(string str in src)
  {
    bool withWWW;
    if(dict.TryGetValue(str, out withWWW))
    {
      if(withWWW)
        continue;
      if(str.StartsWith("www."))
      {
        dict[str] = true;
        yield return str;
      }
    }
    else
    {
      if(dict[str] = str.StartsWith("www."))
        yield return str;
    }
  }
  foreach(var kvp in dict)
    if(!kvp.Value)
      yield return kvp.Key;
}

这样,我们在看到这些表单时立即传递以"www."开头的表单,只有那些不以它开头的表单才需要等待整个列表被处理。