使用另一个字典过滤字典-.Net LINQ

4

我有两个相同类型的字典,A和B。

Dictionary<string, IEnumerable<object>>

我正在使用对象来表示一个具有属性'Id'的复杂类型。

我正在查找所有在A中具有存在于B中的对象(使用Id),但在不同的键下。这基本上是告诉我们一个对象是否已经更改了键。A是新字典,B是旧字典。

是否有一种合理的方法使用LINQ来完成这个任务?我希望结果是满足条件的A中所有键值对的字典。先谢谢。


5
可以请提供一个例子和您目前尝试的代码吗? - Mehrdad Dowlatabadi
1
你只想要移动的对象,还是如果任何一个对象移动了,就要整个列表 A - Rufus L
1
尝试在没有LINQ的情况下解决问题,然后将其转换为LINQ。这种方法更容易理解和实现。顺便说一句,LINQ只是让代码更短,但对性能没有帮助。有时候甚至会因为动态表达式树而使程序变慢。 - brainless coder
我正在寻找A中所有具有存在于B中的对象(使用Id),但在不同键下的项目。基本上是为了确定对象是否移动了键。你能否更好地解释一下并为我们提供一个例子? - Marco Salerno
寻找存在于两个字典中(如果将它们的值展开)但与不同键相关联的对象。 - tmaurst
4个回答

2

我使用IHasId接口来使用Id属性:

public interface IHasId
{
    int Id { get; }
}

继承了该接口的AAA类:

public class AAA: IHasId
{
    public int Id { get; set; }
}

这是你要找的linq:

Dictionary<string, IEnumerable<IHasId>> A = new Dictionary<string, IEnumerable<IHasId>>();
A.Add("111", new List<IHasId> { new AAA { Id = 1 }, new AAA { Id = 2 } });
A.Add("333", new List<IHasId> { new AAA { Id = 3 } });
Dictionary<string, IEnumerable<IHasId>> B = new Dictionary<string, IEnumerable<IHasId>>();
B.Add("111", new List<IHasId> { new AAA { Id = 1 }});
B.Add("222", new List<IHasId> { new AAA { Id = 2 }});
B.Add("333", new List<IHasId> { new AAA { Id = 3 } });

var res = A.Where(a => a.Value.Any(c => B.Any(v => v.Value
           .Select(x => x.Id).Contains(c.Id) && a.Key != v.Key))).ToList();

在这个例子中,它返回键为111的对象,该对象的ID = 2,从键222移动到键111。
如果你想要字典形式的结果,可以将ToList更改为ToDictionary:
var res = A.Where(a => a.Value.Any(c => B.Any(v => v.Value
           .Select(x => x.Id).Contains(c.Id) && a.Key != v.Key)))
           .ToDictionary(a=>a.Key, a=>a.Value);

如果你想在新的字典中只保留发生更改的值,就像示例中的键111和仅具有Id = 2的对象的值一样,可以按照以下方式实现:

var res = A.Select(a => new KeyValuePair<string, IEnumerable<IHasId>>(a.Key, 
           a.Value.Where(c => B.Any(v => v.Value.Select(x => x.Id).Contains(c.Id) && a.Key != v.Key))))
           .Where(a=>a.Value.Count() > 0)
           .ToDictionary(a => a.Key, a => a.Value);

我将代码更改为返回KeyValuePair列表,如果您不想要它作为列表,请在结尾处删除ToList()。 - s-s
未来,这是返回移动对象的先前Linq:var res = A.SelectMany(a => a.Value.Where(c => B.Any(v => v.Value.Select(x => x.Id).Contains(c.Id) && a.Key != v.Key))).ToList(); - s-s

2
就可搜索性而言,您的字典是反过来的;它对于在给定字符串的情况下查找对象非常有效,但您需要能够查找给定对象的字符串。用于此目的的高效数据结构将是Lookup<object,string>
首先,使用ToLookup()创建一个查找表,其中键是对象,值是列表A和B中的键列表。使用Union(而不是Concat)来消除重复项。
var lookup = listA
    .Union( listB )
    .ToLookup( pair => pair.Value, pair => pair.Key );

一旦你得到了查找表,问题就变得很简单。
var results = lookup.Where( x => x.Count() > 1);

请参见带有示例数据的这个DotNetFiddle,以获取一个工作示例。

1
如果您需要原始对象的A条目,可以这样做:
var result = A.Where(a => B.Any(b => b.Key != a.Key && b.Value.Intersect(a.Value).Any()));

如果您需要从B中仅匹配的对象获取A条记录,则可能是这样的:
var result = A.Select(a => new KeyValuePair<string, IEnumerable<object>>(a.Key, B.Where(b => b.Key != a.Key).SelectMany(b => b.Value.Intersect(a.Value)))).Where(x => x.Value.Any());


您可以为Intersect提供自定义的相等比较器以通过ID或其他方式匹配项目。
如果需要将其作为字典使用,请使用new Dictionary<string, IEnumerable<object>>(result)

0
使用Join运算符(参见join clause (C# Reference)):
var dictionary = (        
        from a in (from entry in A from Value in entry.Value select new { entry.Key, Value }) 
        join b in (from entry in B from Value in entry.Value select new { entry.Key, Value }) 
        on ((dynamic)a.Value).Id equals ((dynamic)b.Value).Id
        where a.Key != b.Key 
        select a
    ).ToDictionary(a => a.Key, a => a.Value);

尝试过这个,但似乎没有考虑到字典值是IEnumerable的情况。 - tmaurst
@tmaurst 我之前忽略了这一点。我已经更正了答案以考虑到这一点。我确认当A和B都是Dictionary<string,IEnumerable<object>>时它可以工作。 - Marcelo Estriga

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