如何在C#元组列表中删除重复的反向元组

3

假设我有一个元组列表如下:

    List<Tuple<string, string>> conflicts = new List<Tuple<string, string>>();
    conflicts.Add(new Tuple<string, string>("Maths", "English"));
    conflicts.Add(new Tuple<string, string>("Science", "French"));
    conflicts.Add(new Tuple<string, string>("French", "Science"));
    conflicts.Add(new Tuple<string, string>("English", "Maths"));

我想检查元组列表中的反向重复项并删除它们,我该如何使用循环实现?
注意:我所说的反向重复是指“英语”,“数学”和“数学”,“英语”的重复。
注意:我的代码中的元组是使用SqlDataReader填充的,但我上面使用的示例与其布局非常接近。
这似乎非常简单,但我整晚都被难住了。

1
你想要删除两个重复的,还是只保留一个? - Wai Ha Lee
7个回答

5

使用自定义的IEqualityComparer

public class TupleComparer : IEqualityComparer<Tuple<string, string>>
{
    public bool Equals(Tuple<string, string> x, Tuple<string, string> y)
    {
        return  (x.Item1 == y.Item1 && x.Item2 == y.Item2) ||
                (x.Item1 == y.Item2 && x.Item2 == y.Item1);
    }

    public int GetHashCode(Tuple<string, string> obj)
    {
        return string.Concat(new string[] { obj.Item1, obj.Item2 }.OrderBy(x => x)).GetHashCode();
        //or
        //return (string.Compare(obj.Item1, obj.Item2) < 0 ? obj.Item1 + obj.Item2 : obj.Item2 + obj.Item1).GetHashCode(); 
    }
}

您可以使用 HashSet<Tuple<string, string>> 替代 List<Tuple<string, string>>
var conflicts = new HashSet<Tuple<string, string>>(new TupleComparer());
conflicts.Add(new Tuple<string, string>("Maths", "English"));
conflicts.Add(new Tuple<string, string>("Science", "French"));
conflicts.Add(new Tuple<string, string>("French", "Science"));
conflicts.Add(new Tuple<string, string>("English", "Maths"));

4
List<Tuple<string, string>> conflicts = new List<Tuple<string, string>>();
List<Tuple<string, string>> noConflicts = new List<Tuple<string, string>>();

conflicts.Add(new Tuple<string, string>("Maths", "English"));
conflicts.Add(new Tuple<string, string>("Science", "French"));
conflicts.Add(new Tuple<string, string>("French", "Science"));
conflicts.Add(new Tuple<string, string>("English", "Maths"));

foreach(Tuple<string,string> t in conflicts)
{
      if(!noConflicts.Contains(t) && !noConflicts.Contains(new Tuple<string,string>(t.Item2,t.Item1)))
           noConflicts.Add(t);
}

foreach(Tuple<string, string> t in noConflicts)
       Console.WriteLine(t.Item1 + "," + t.Item2);

我确定还有更好的方法,但这个方法能够运行。 Output

谢谢你的回答,也许其他解决方案更加简洁,但是在我目前的能力水平下,你的回答最为直接易懂,因此我将其标记为被采纳的答案。保重。 - Dude365

3
一种比较简单的实现方式:
var distinct =
    conflicts
        .GroupBy(
            x =>
                {
                    var ordered = new[] { x.Item1, x.Item2 }.OrderBy(i => i);
                    return
                        new
                        {
                            Item1 = ordered.First(),
                            Item2 = ordered.Last(),
                        };
                })
        .Distinct()
        .Select(g => g.First())
        .Dump();

它对元组中的项进行排序,以便Maths、English和Engilsh、Maths是相同的,然后将它们放入匿名类型(再次称为Item1/2),然后依赖于匿名类型的结构相等性来执行去重操作,最后只需从每个组中提取第一个元组。

1
问题在于您误用了 Tuple<T,Y>。如果 { "Math", "Science" }{ "Science" , "Math" } 是可以互换的,那么它们就不是一对。您更像是使用了一个 string[2]。例如,在 Dictionary 中,它是一个 Tuple<TKey,TValue>,它们是有意义地分开的东西,具有适当的配对关系,并且不仅仅是数据列表。

尝试使用类似 List<List<string>> 的东西来更好地表示您的数据,并允许您访问有用的 List<T> 答案,如 this one。或者确实使用 List<Conflict>,其中 Conflict 包含一个 List,其中顺序对等式没有影响。


1

LINQ一行代码。真是太棒了。

var noConflicts = conflicts.Select(c => new HashSet<string>() { c.Item1, c.Item2})
    .Distinct(HashSet<string>.CreateSetComparer())
    .Select(h => new Tuple<string, string>(h.First(), h.Last()));

这个功能通过将所有内容发送到一个HashSet<T>中实现,该集合具有CreateSetComparer()方法,可以无视顺序进行Distinct()操作。

0
using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {

        var conflicts = new List<Tuple<string, string>>();
        conflicts.Add(new Tuple<string, string>("Maths", "English"));
        conflicts.Add(new Tuple<string, string>("Science", "French"));
        conflicts.Add(new Tuple<string, string>("French", "Science"));
        conflicts.Add(new Tuple<string, string>("English", "Maths"));

        RemoveDupes(conflicts);
        foreach(var i in conflicts) Console.WriteLine(i.Item1 + " " + i.Item2);

    }

    public static void RemoveDupes(List<Tuple<string, string>> collection){
        var duplicates = collection
            // indescriminate which value comes first
            .Select((x, i) => new{ Item= new Tuple<string,string>(x.Item2.IsGreaterThan(x.Item1) ? x.Item2 : x.Item1, 
                                                                  x.Item2.IsGreaterThan(x.Item1) ? x.Item1 : x.Item2), Index = i})
            // group on the now indescrimitate values
            .GroupBy(x => x.Item)
            // find duplicates
            .Where(x => x.Count() > 1)
            .Select(x => new {Items = x, Count=x.Count()})
            // select all indexes but first
            .SelectMany( x =>
                x.Items.Select( b => b)
                       .Zip(Enumerable.Range( 1, x.Count ),
                            ( j, i ) => new { Item = j, RowNumber = i }
                )
            ).Where(x => x.RowNumber != 1);
        foreach(var item in duplicates){
            collection.RemoveAt(item.Item.Index);
        }
    }


}

public static class Ext{
    public static bool IsGreaterThan(this string val, string compare){
        return val.CompareTo(compare) == 1;
    }
}

0

避免表示的AB/BA歧义最好的方法是拥有不允许其存在的数据模型。通过强制约束条件,您可以在数据库中实现这一点,这是广泛使用的方法。如果我们说元组是有序的,就不会出现任何歧义。

public class Ordered2StrTuple : Tuple<string, string> 
{
    public Ordered2StrTuple(string a, string b)
        : this(a, b, String.CompareOrdinal(a,b))
    { }

    private Ordered2StrTuple(string a, string b, int cmp)
        : base(cmp > 0 ? b : a, cmp > 0 ? a : b)
    { }
}

现在任务非常简单:

var noConflicts = conflicts
    .Select(s => new Ordered2StrTuple(s.Item1, s.Item2))
    .Distinct();

为了与Equal一致,比较需要是序数的,因此我删除了这里的通用版本。如果您只想进行一次去重复操作,可以像这样执行:

var noConflicts = conflicts.Select(t =>
    String.CompareOrdinal(t.Item1, t.Item2) > 0 ? new Tuple<string, string>(t.Item2, t.Item1) : t
    ).Distinct();

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