我有两个词典Dictionary<string, object>
。我需要找到它们的交集(我的意思只是它们的键交叉)和A \ B和B \ A减法并对对象做一些动作(事实上我的对象是EntityFramework实体,我必须将它们的状态标记为{{1}分别是},Modified
和Added
,尽管它与问题不太相关)。想象一下最简单的Venn diagram。
我想以最有效的方式做到这一点。我想我有两个选择:
1)实现一组通用扩展方法,在Deleted
IEnumerable
上内部操作KeyCollection
方法,例如:
ExceptByKey
然后我可以操作这些方法来分别处理三组中的每一组。从there我知道public static Dictionary<TKey, TValue> ExceptByKeys<TKey, TValue>(this Dictionary<TKey, TValue> dict1, Dictionary<TKey, TValue> dict2)
{
return dict1.Keys.Except(dict2.Keys).ToDictionary(key => key, key => dict1[key]);
}
方法在内部使用KeyCollection.Contains
方法,因此两者都是O(1)。那么我的Dictionary<TKey, TValue>.ContainsKey
方法将在O(n)中运行,是正确的吗?我需要为每个字典使用一次,并以某种方式检测相交的部分,这可以完成隐含地通过首先迭代一个字典中的所有实体并将它们标记为属于交集。那么,它是否像O(n)+ O(n + m)?
2)我还可以遍历我的词典,在每个元素的另一个字典上调用Except
方法并执行相应的操作。这对我来说似乎是一个更好的解决方案,因为我只能获得O(n + m)复杂度。
所以,问题是: - 我在计算中是对的吗? - 有没有更好的方法,我没想过要完成我想要的东西?
更新2015年6月19日
所以我选择了第二种情况,它运作正常。这是我在野外的实现
ContainsKey
答案 0 :(得分:1)
你的推理看起来很合理。 LINQ Except()
迭代第二个集合,将其放入 HashSet
Set
,然后迭代第一个集合,对Set
执行查找 - 它是O(n + m)。因此,您的扩展方法也是O(n + m)。正如您所提到的,如果您想要计算3组添加,删除和交叉点,则必须多次调用它,使选项2更为可取。
您正在尝试进行外部联接,并且能够分别评估左侧,内侧和右侧项目。对于O(n + m)解决方案,您可以使用类似这样的
public static JoinResult<TKey> JoinKeys<TKey, TValue>(this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
{
var left = new List<TKey>();
var inner = new HashSet<TKey>(); // HashSet to optimize lookups
var right = new List<TKey>();
foreach (var l in first.Keys) // O(n)
{
if (second.ContainsKey(l))
inner.Add(l);
else
left.Add(l);
}
foreach (var r in second.Keys) // O(m)
{
if (!inner.Contains(r))
right.Add(r);
}
return new JoinResult<TKey>
{
Left = left,
Inner = inner,
Right = right
};
}
public class JoinResult<T>
{
public IEnumerable<T> Left { get; set; }
public IEnumerable<T> Inner { get; set; }
public IEnumerable<T> Right { get; set; }
}