在列表中查找满足特定条件的重复项

4

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

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

                            }

如果您不介意使用IComparer和IEqualityComparer来解决问题,请看看我最终发布的代码(在Daniel的帮助下纠正了一些错误)。 - Skyrim
4个回答

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

顺便提一下:您可以通过将代码更改为以下内容来节省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
                                              };
                                      });

@Sam1:我不确定我理解了。 "mytest.com"已经是一个没有重复和没有前缀“www”的域名,正如你所看到的,它已经返回了。但是回答你的问题:是的,它也会返回"ddd.com"。 - Daniel Hilgarth
还有一件事:您已经命名了两个不同的变量result。也许您应该尝试为其中一个重命名,以便代码可以编译。 - Cristian Lupascu
@DanielHilgarth,请查看上面的编辑,它没有返回正确的值给我... - Zaki
@DanielHilgarth 谢谢,我已经纠正了,现在得到的是 www.test.com,www.test2.com,test3.com,www.test3.com,test4.com ... 我应该得到 www.test3.com... - Zaki
显示剩余6条评论

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

这已经通过了我所有的测试。再次感谢 :)


这个程序不能正常工作。只要URL以X、Y或Z开头,它就会失败。请尝试将“www.zzz.com”和“zzz.com”添加到域名列表中进行测试。 - Daniel Hilgarth
@DanielHilgarth 说得好,让我看看我是否可以修改它...或者不改 :P - Skyrim
@DanielHilgarth - 抱歉,我把那个放错了帖子:p -- 我认为这个更新的可以用...请告诉我你的想法! - Skyrim
它可以工作。但是,我不会使用它,因为有两个原因:它可以用更少的代码实现(请参见我的答案),并且它重新排序了项目。 - Daniel Hilgarth
@DanielHilgarth - 代码并不是真正困扰我的问题(因为它是一致且易于理解的)...然而重新排序是一个令人讨厌的瑕疵。 - Skyrim
@DanielHilgarth - 好的,我已经把它放好了!现在多余的代码怎么办...我该如何摆脱它并使其工作 :-D (开个玩笑) - Skyrim

0

编辑:请查看下面的Daniel的回复。我在这个问题上有点匆忙。

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

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

应该可以工作!

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

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

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


粗体文本在代码块中不起作用 ;) 而且给出的解决方案实际上并不符合标准,因为第一个结果应该是 www.test.com - Nuffin
我不知道你在说什么... ;) - Anders Arpi
但这不会返回 www.test.com - V4Vendetta
@AndersHolmström:请再次阅读问题。这不是OP想要的。 - Daniel Hilgarth
哎呀,你当然是完全正确的,丹尼尔。又是一天让我在 Stack Overflow 上感到谦卑的经历。 :) - Anders Arpi
@Andres:在上面的Linq中,您正在替换www...如果我这样做,我将失去我要选择的一个示例。例如,如果www.test.com的ID为1,test.com的ID为2,则通过执行您的linq,我将不知道哪个域名前面有www...希望这很清楚。 - Zaki

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."开头的表单时立即将它们传递出去,只有那些不以此开头的表单才需要等待整个列表被处理。


这个不正确。它返回没有“www。”的域名,只要它出现在带有“www。”的域名之前。例如输入:new List<string>{ "www.test.com", "test.com", "mytest.com", "abc.com", "www.abc.com"};。你的输出是:"www.test.com", "mytest.com", "abc.com"。正确的输出应该是:"www.test.com", "mytest.com", "www.abc.com" - Daniel Hilgarth
@DanielHilgarth 哎呀,我错过了这个要求,只是应该在这个基础上有所区别。已经编辑好了。 - Jon Hanna
@DanielHilgarth 什么,重新排序?谁在乎呢? - Jon Hanna
@JonHanna:我不知道他是否在意... :-) 如果新列表将显示在UI中,如果它与以前完全不同的顺序,我会在意。 - Daniel Hilgarth
@JonHanna - 你可以使用IComparer和OrderBy调用来微调它,以便保留顺序。请参考我的解决方案。 - Skyrim
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接