在C#中从列表中选择唯一元素

27
如何从列表 {0, 1, 2, 2, 2, 3, 4, 4, 5} 中选择独特的元素,以获得{0, 1, 3, 5},有效地删除所有重复的元素{2, 4}?

至少在C#之外(我不能确定C#本身),如果起始点包含重复项,则其实际上不是一个集合。它可能是一个多重集合,或者是一个列表,或者... - Jonathan Leffler
10个回答

38
var numbers = new[] { 0, 1, 2, 2, 2, 3, 4, 4, 5 };

var uniqueNumbers =
    from n in numbers
    group n by n into nGroup
    where nGroup.Count() == 1
    select nGroup.Key;

// { 0, 1, 3, 5 }

4
哇塞,真高级。那么 HashSet<int> r = new HashSet<int>(numbers); 怎么样? - tymtam
2
@Tymek:OP想要删除重复项,只保留原始序列中唯一的数字。 - Bryan Watts
我迷失了。你提供的代码没有修改原始序列名为“numbers”的内容。 - tymtam
1
@Tymek:OP要求选择唯一的数字,这意味着通过检查现有序列来创建新序列。 - Bryan Watts
3
非常接近。正确答案应该是{0, 1, 3, 5},因为只有2和4重复。但我想你已经明白了意思。 - Bryan Watts
显示剩余2条评论

20
var nums = new int{ 0...4,4,5};
var distinct = nums.Distinct();

请确保您正在使用Linq和.NET Framework 3.5。


3
这会返回包括重复元素在内的 {0, 1, 2, 3, 4, 5}。 - Ozgur Ozcitak
哦,我的错误。我没有注意到你想要删除那些重复的条目。 - CVertex

14

With lambda..

var all = new[] {0,1,1,2,3,4,4,4,5,6,7,8,8}.ToList();
var unique = all.GroupBy(i => i).Where(i => i.Count() == 1).Select(i=>i.Key);

请注意,如果您想要使用此解决方案处理对象,并获取具有特定键/字段的对象列表(该键/字段是唯一的),则需要将最后一个Select替换为SelectMany:.SelectMany(i => i),这将使IEnumerable<IGrouping<TKey, TElement>>扁平化为IEnumerable<T> - zcoop98
另一种使用对象或其他方式完成此操作的方法是使用.Select(x => x.FirstOrDefault())替换Where和Select(从此博客文章中获取)。 - zcoop98

10

C# 2.0解决方案:

static IEnumerable<T> GetUniques<T>(IEnumerable<T> things)
{
    Dictionary<T, int> counts = new Dictionary<T, int>();

    foreach (T item in things)
    {
        int count;
        if (counts.TryGetValue(item, out count))
            counts[item] = ++count;
        else
            counts.Add(item, 1);
    }

    foreach (KeyValuePair<T, int> kvp in counts)
    {
        if (kvp.Value == 1)
            yield return kvp.Key;
    }
}

这将抛出一个KeyNotFoundException。 - David Wengier
1
这个代码是可以工作的,但你需要将 counts[item]++; 改为 if (counts.ContainsKey(item)) counts[item]++; else counts.Add(item, 1); - Ozgur Ozcitak

9

如果您的列表中有复杂类型对象,并且想要获取属性的唯一值,以下是另一种可行的方法:

var uniqueValues= myItems.Select(k => k.MyProperty)
                  .GroupBy(g => g)
                  .Where(c => c.Count() == 1)
                  .Select(k => k.Key)
                  .ToList();

或者获取不同的值:

var distinctValues = myItems.Select(p => p.MyProperty)
                            .Distinct()
                            .ToList();

如果您的属性也是复杂类型,则可以为Distinct()创建自定义比较器,例如Distinct(OrderComparer),其中OrderComparer可能如下所示:
public class OrderComparer : IEqualityComparer<Order>
{
    public bool Equals(Order o1, Order o2)
    {
        return o1.OrderID == o2.OrderID;
    }

    public int GetHashCode(Order obj)
    {
        return obj.OrderID.GetHashCode();
    }
}

1
我更喜欢这个。使用lambda表达式更短,更易读(主观?)。 - Bahamut
@EwaldStieger,他不想为每个值留下单个实例,他想要删除所有出现超过1次的值的实例。那么Distinct()如何实现这一点呢?(我对你的回答获得了8个赞感到惊讶,因为它并没有正确解决问题。) - Massimiliano Kraus
@MassimilianoKraus 是的,你说得对。我在原始答案中漏掉了那个部分,现在已经更新了。 - Ewald Stieger

3
如果您无法使用Linq,因为您需要支持无法升级的旧代码,则可以声明一个Dictionary,其中第一个int是数字,第二个int是出现次数。遍历您的List,加载您的Dictionary。完成后,遍历您的Dictionary,仅选择出现次数为1的元素。

2

有很多种方法可以解决问题,但HashSet似乎是最适合这个任务的。

var numbers = new[] { 0, 1, 2, 2, 2, 3, 4, 4, 5 };

HashSet<int> r = new HashSet<int>(numbers);

foreach( int i in r ) {
    Console.Write( "{0} ", i );
}

输出结果:
0 1 2 3 4 5

谢谢您的回复,但我想从原始列表中删除所有重复元素,例如:{0, 1, 1, 2, 2, 3} -> {0, 3} - Ozgur Ozcitak
你想修改的是一个 List 类的对象吗? - tymtam

2
我相信Matt的意思是:
 static IEnumerable<T> GetUniques<T>(IEnumerable<T> things)
 {
     Dictionary<T, bool> uniques = new Dictionary<T, bool>();
     foreach (T item in things)
     {
         if (!(uniques.ContainsKey(item)))
         {
             uniques.Add(item, true);
         }
     }
     return uniques.Keys;
 }

这是CVertex发布的.NET 2.0版本,它还返回重复的元素。 - Ozgur Ozcitak
不,我更喜欢让它们保持原样(就像法国人说的英国人偶尔会枪杀一位海军上将)以激励其他人。 - Robert Rossney

0

这里有一个没有使用LINQ的解决方案:

var numbers = new[] { 0, 1, 2, 2, 2, 3, 4, 4, 5 };

// This assumes the numbers are sorted
var noRepeats = new List<int>();
int temp = numbers[0]; // Or .First() if using IEnumerable
var count = 1;
for(int i = 1; i < numbers.Length; i++) // Or foreach (var n in numbers.Skip(1)) if using IEnumerable
{
    if (numbers[i] == temp) count++;
    else
    {
        if(count == 1) noRepeats.Add(temp);
        temp = numbers[i];
        count = 1;
    }
}
if(count == 1) noRepeats.Add(temp);

Console.WriteLine($"[{string.Join(separator: ",", values: numbers)}] -> [{string.Join(separator: ",", values: noRepeats)}]");

这将打印:

[0,1,2,2,2,3,4,4,5] -> [0,1,3,5]

-1

在 .Net 2.0 中,我对这个解决方案非常有把握:

public IEnumerable<T> Distinct<T>(IEnumerable<T> source)
{
     List<T> uniques = new List<T>();
     foreach (T item in source)
     {
         if (!uniques.Contains(item)) uniques.Add(item);
     }
     return uniques;
}

1
但是这种方法会添加每个存在的值。他不想要这样。他希望完全删除重复的值。 - Massimiliano Kraus

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