简体   繁体   English

LINQ中具有多个实例/ IEqualityComparer问题的平等比较

[英]Equality Comparison with Multiple Instances/IEqualityComparer problems in LINQ

This is similar to my last question; 类似于我的最后一个问题。 but from a different angle. 但是从另一个角度来看。 See if item exists once in Enumerable (Linq) 查看项目是否在Enumerable(Linq)中存在一次

Given the following set of items, and lists containing them... 给定以下一组项目以及包含这些项目的列表...

Item 1
Item 2
Item 3
Item 4
Item 5

class Item
{
 string Name { get; set; }
}

List<Item> available = new List<Item>()
{
 Item 1
 Item 1
 Item 2
 Item 3
 Item 5
}

List<Item> selected = new List<Item>()
{
 Item 1
 Item 2
 Item 3
}

I need to make a third List that has everything from "available", except what is in "selected". 我需要制作第三个列表,其中包含“可用”中的所有内容,但“选定”中的内容除外。 However 'Item 1' is in 'available' twice, but only in 'selected' once. 但是,“项1”在“可用”中两次,但仅在“选定”中一次。 Since they are instances of the same item, I am having trouble figuring out the appropriate logic to accomodate this. 由于它们是同一项目的实例,因此我很难找出适当的逻辑来适应这一点。

The final array should look like... 最终的数组应该看起来像...

List<Item> selectable = new List<Item>()
{
 Item 1
 Item5
}

This is a kinda tricky approach, but it gets the job done. 这是一种棘手的方法,但是可以完成工作。 I borrowed from the "Decorate-Sort-Undecorate" idiom in Python, which does sorting by associating a temporary sort key with an array, combined with the fun and useful fact that anonymous types in .NET have a default EqualityComparer that compares based on the values of their fields. 我从Python的“ Decorate-Sort-Undecorate”惯用语中借用,它通过将临时排序键与数组相关联来进行排序,并结合了一个有趣而有用的事实:.NET中的匿名类型具有默认的EqualityComparer,该默认EqualComparer基于其字段的值。

Step 1: Group the elements in each list by Name, and then associate an index with each item within each group , and flatten the groups back into a regular list: 第1步:按名称对每个列表中的元素进行分组,然后将索引与每个组中的每个项目相关联,然后将这些组展平到常规列表中:

var indexedAvailable = available.GroupBy(i => i.Name)
                                .SelectMany(g => g.Select((itm, idx) => new 
                                              { Name = itm.Name, Index = idx }));
var indexedSelected = selected.GroupBy(i => i.Name)
                              .SelectMany(g => g.Select((itm, idx) => new
                                              { Name = itm.Name, Index = idx }));

This will turn the lists into these: 这会将列表变成这些:

indexedAvailable            indexedSelected
Name = Item 1, Index = 0    Name = Item 1, Index = 0
Name = Item 1, Index = 1    Name = Item 2, Index = 0
Name = Item 2, Index = 0    Name = Item 3, Index = 0
Name = Item 3, Index = 0
Name = Item 5, Index = 0

Now you can see that in the indexed lists, each numbered occurrence of any Name in available will match only with the equivalent-numbered occurrence of the same name in selected . 现在您可以看到,在索引列表中, available的任何Name的每个编号出现都只会与selected相同名称的等编号出现匹配。 So you can use a simple Except to remove anything in indexedAvailable that isn't in indexedSelected , and then "undecorate' by turning the anonymous-typed indexed objects back into Item s. 因此,您可以使用简单的Except删除indexedAvailableindexedAvailable中没有的indexedSelected ,然后通过将匿名类型的索引对象变回Item来“取消装饰”。

var selectable = indexedAvailable.Except(indexedSelected)
                                 .Select(i => new Item() { Name = i.Name });

And the proof: 并证明:

foreach (var item in selectable)
    Console.WriteLine(item.Name);
//prints out:
//Item 1
//Item 5

Note that this will work even if selected contains names that aren't in available , eg if the second list has an Item 4 like in your last question. 请注意,这会工作,即使selected包含不在名称available ,例如,如果第二列表中有一个Item 4就像在你的最后一个问题。

There may be a LINQ method to accomplish this task. 可能有一种LINQ方法可以完成此任务。 You could certainly get the 5 item since it is unique, but the second item 1 could prove difficult. 您肯定可以得到5项,因为它是唯一的,但是第二项1可能很难。 However, you could always do this the old fashioned way and create the new list yourself. 但是,您总是可以用老式的方式进行操作,并自己创建新的列表。 Consider this example: 考虑以下示例:

class Item
{
    public Item(string name) { Name = name; }
    public string Name { get; set; }
}

... ...

List<Item> available = new List<Item>()
{
    new Item("1"), new Item("1"), new Item("2"), new Item("3"), new Item("5")
};

List<Item> selected = new List<Item>()
{
    new Item("1"),new Item("2"), new Item("3")
};

List<Item> stillAvailable = new List<Item>();
List<Item> stillSelected = new List<Item>(selected);

foreach (Item item in available)
{
    Item temp = stillSelected.Find(i => i.Name == item.Name);
    if (temp == null)
        stillAvailable.Add(item);
    else 
        stillSelected.Remove(temp);
}

You create a list for the items still available, which is initially empty. 您为仍然可用的项目创建一个列表,该列表最初为空。 You create a list for the items still selected, which contains all of the selected items. 您为仍选中的项目创建一个列表,其中包含所有选中的项目。 You then simply iterate over the available items and search the stillSelected list. 然后,您只需遍历可用项目并搜索stillSelected列表。 If the item is found, you remove it from the stillSelected list. 如果找到该项目,则将其从stillSelected列表中删除。 If not, you add it to the stillAvailable list. 如果不是,则将其添加到stillAvailable列表中。 By the end of the loop, stillAvailable will contain a single item 1 and item 5. 在循环结束时,stillAvailable将包含单个项目1和项目5。

var comp = new MyEqualityComparer();
selectable = available.Distinct(comp).Except(selected, comp);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM