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

时间:2012-01-12 10:15:07

标签: c# linq list

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

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

                            }

4 个答案:

答案 0 :(得分:5)

我想你想要这样的东西:

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

BTW:您可以通过将代码更改为此来保存自己的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
                                              };
                                      });

答案 1 :(得分:1)

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

    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();

这已通过我的所有测试。第二次干杯:)

答案 2 :(得分:0)

修改:请参阅下面的Daniel的回复。我对这个有点太匆匆了。

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

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

应该工作!

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

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

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

答案 3 :(得分:0)

重新定义平等意味着什么(通常是你在这里做的)的正常.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."开始传递这些表单,只有那些不以它开头的表单必须等待处理整个列表。