在C#中合并字典


493

Dictionary<T1,T2>在C#中合并2个或更多词典()的最佳方法是什么?(像LINQ这样的3.0功能很好)。

我正在考虑以下方法签名:

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(Dictionary<TKey,TValue>[] dictionaries);

要么

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(IEnumerable<Dictionary<TKey,TValue>> dictionaries);

编辑:从Jare​​dPar和Jon Skeet得到了一个很酷的解决方案,但是我正在考虑处理重复键的东西。在发生冲突的情况下,只要保持一致,就可以将哪个值保存到字典中。


174
无关,但是对于希望合并两个词典而不进行重复键检查的任何人,这很好用:dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
Benjol 2011年

6
@Benjol,您可以在答案部分中添加此内容
M.Kumaran 2013年

4
Clojure在C#中的合并:dict1.Concat(dict2).GroupBy(p => p.Key).ToDictionary(g => g.Key, g => g.Last().Value)
Bruce 2015年

@Benjol:通过使用首选第一个词典中的条目来消除重复dictA.Concat(dictB.Where(kvp => !dictA.ContainsKey(kvp.Key))).ToDictionary(kvp=> kvp.Key, kvp => kvp.Value)
Suncat2000

Answers:


320

这部分取决于您遇到重复时要发生的情况。例如,您可以执行以下操作:

var result = dictionaries.SelectMany(dict => dict)
                         .ToDictionary(pair => pair.Key, pair => pair.Value);

如果您得到任何重复的密钥,将抛出异常。

编辑:如果您使用ToLookup,那么您将获得一个查找,该查找每个键可以具有多个值。然后,您可以将其转换为字典:

var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());

这有点难看-而且效率低下-但就代码而言,这是最快的方法。(坦率地说,我还没有测试过。)

您当然可以编写自己的ToDictionary2扩展方法(使用一个更好的名称,但是我现在没有时间考虑)-这样做并不是很难,只需覆盖(或忽略)重复的键即可。(在我看来)重要的一点是使用SelectMany,并意识到字典支持在其键/值对上进行迭代。


3
要实际合并值,而不只是从第一个字典中获取值,您可以在Jon Skeet的编辑答案中将group => group.First()替换为group => group.SelectMany(value => value)。
andreialecu 2011年

3
现在我想知道GroupBy是否比ToLookup更合适?我认为ILookup只是在IGrouping之上添加了一个索引器,size属性并包含方法-因此它要快一点吗?
13年

2
@toong:说实话,两者都应该不错。使用GroupBy确实可以提高效率-但我怀疑这两种方法是否有意义。
乔恩·斯基特

1
@Joe:通过将比较器提供给ToDictionary方法调用,这很简单。不过,我更希望在更常见的情况下简化答案,并希望需要自定义比较器的任何人都能找到相关的重载。
乔恩·斯基特

1
@Serge:我建议您提出一个具有精确要求的新问题。
乔恩·斯基特

264

我会这样做:

dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value));

简单容易。根据此博客文章,它的速度甚至比大多数循环还要快,因为其底层实现通过索引而不是枚举器访问元素(请参见此答案)

如果有重复项,它当然会引发异常,因此您必须在合并之前进行检查。


169
如果重复很重要,请使用dictionaryFrom.ToList().ForEach(x => dictionaryTo[x.Key] = x.Value)。这样,来自的值将dictionaryFrom覆盖可能存在的键的值。
okrumnow'2

7
这不太可能比循环快-您忘记了ToList()实际上最终会复制字典。编码更快!;-)
索伦Boisen

6
因为ToList()实际上并没有最终复制字典,所以它只是复制其中的元素引用。基本上,列表对象本身将在内存中有一个新地址,对象是相同的!
GuruC


1
嗯,优于乔恩斯基特的答案,我相信。
Sнаđошƒаӽ

102

如果有多个键(“右”键代替“左”键),则不会爆炸,可以合并多个词典(如果需要)并保留类型(但需要一个有意义的默认公共构造函数的限制):

public static class DictionaryExtensions
{
    // Works in C#3/VS2008:
    // Returns a new dictionary of this ... others merged leftward.
    // Keeps the type of 'this', which must be default-instantiable.
    // Example: 
    //   result = map.MergeLeft(other1, other2, ...)
    public static T MergeLeft<T,K,V>(this T me, params IDictionary<K,V>[] others)
        where T : IDictionary<K,V>, new()
    {
        T newMap = new T();
        foreach (IDictionary<K,V> src in
            (new List<IDictionary<K,V>> { me }).Concat(others)) {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K,V> p in src) {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

}

做得好。这正是我所需要的。很好地使用泛型。同意该语法有点尴尬,但是您对此无能为力。
彼得M,2010年

2
我喜欢这种解决方案,但有一个警告:如果this T me使用new Dictionary<string, T>(StringComparer.InvariantCultureIgnoreCase)或类似方法声明了字典,则所得字典将不会保留相同的行为。
若昂·波特拉

3
如果你做me一个Dictionary<K,V>,然后你可以做var newMap = new Dictionary<TKey, TValue>(me, me.Comparer);,你也避免了额外的T类型参数。
ANeves 2014年

1
有人有使用这种野兽的例子吗?
蒂姆(Tim)

1
@Tim:我为下面的野兽添加了一个示例,我添加了ANeves的解决方案,结果得到了更加驯化的野兽:)
keni 2015年

50

简单的解决方案是:

using System.Collections.Generic;
...
public static Dictionary<TKey, TValue>
    Merge<TKey,TValue>(IEnumerable<Dictionary<TKey, TValue>> dictionaries)
{
    var result = new Dictionary<TKey, TValue>();
    foreach (var dict in dictionaries)
        foreach (var x in dict)
            result[x.Key] = x.Value;
    return result;
}

22

尝试以下

static Dictionary<TKey, TValue>
    Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> enumerable)
{
    return enumerable.SelectMany(x => x).ToDictionary(x => x.Key, y => y.Value);
}

19
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);

1
只是想知道- 联合会不仅有用吗?foreach(var kvp in Message.GetAttachments().Union(mMessage.GetImages()))在生产代码中使用它,如果有任何缺点,请告诉我!:)
火星罗伯逊

1
@Michal:Union将返回一个IEnumerable<KeyValuePair<TKey, TValue>>由键和值的相等性定义的相等性(存在重载以允许其他选择)。这对于一个foreach循环来说是很好的,在这里,这是所有必需的,但是在某些情况下,您实际上需要字典。
蒂莫西

15

我参加聚会很晚,可能会丢失一些东西,但是如果没有重复的键,或者如OP所说,“如果发生冲突,只要将一致”,这是什么问题(将D2合并到D1)?

foreach (KeyValuePair<string,int> item in D2)
            {
                 D1[item.Key] = item.Value;
            }

似乎很简单,也许太简单了,我想知道我是否缺少某些东西。这就是我在某些代码中使用的代码,在这些代码中我知道没有重复的键。不过,我仍在测试中,所以我很想知道我是否正在忽略某些东西,而不是以后再查找。


4
imo最干净,最易读的解决方案之一,它还避免了许多其他解决方案使用的ToList()。
404

15

以下对我有用。如果有重复项,它将使用dictA的值。

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> dictA, IDictionary<TKey, TValue> dictB)
    where TValue : class
{
    return dictA.Keys.Union(dictB.Keys).ToDictionary(k => k, k => dictA.ContainsKey(k) ? dictA[k] : dictB[k]);
}

@EsbenSkovPedersen我认为当我回答这个问题(新的C#开发人员)时,我没有遇到过
Ethan Reesor

我必须删除“ where TValue:class”以将Guid用作值
om471987 2016年

@ om471987是的,Guid是一个结构而不是一个类,因此这很有意义。我认为最初我没有添加该条款时遇到了一些问题。不记得为什么了。
伊桑·里索

10

这是我使用的辅助函数:

using System.Collections.Generic;
namespace HelperMethods
{
    public static class MergeDictionaries
    {
        public static void Merge<TKey, TValue>(this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
        {
            if (second == null || first == null) return;
            foreach (var item in second) 
                if (!first.ContainsKey(item.Key)) 
                    first.Add(item.Key, item.Value);
        }
    }
}

这里列出了很多很棒的解决方案,但是我最喜欢这个解决方案的简单性。只是我个人的喜好。
杰森2012年

3
if(first == null)则此逻辑是无用的,因为first不是ref,并且不会返回。不可能是ref因为它被声明为接口实例。您需要删除错误检查或将其声明为类实例,或者仅返回新字典(不带扩展方法)。
Grault

@Jesdisciple-根据您的建议删除了该问题
Andrew Harry

8

选项1:如果您确定两个字典中都没有重复的键,则这取决于您要执行的操作。比您能做的:

var result = dictionary1.Union(dictionary2).ToDictionary(k => k.Key, v => v.Value)

注意:如果字典中有重复的键,则会引发错误。

选项2:如果可以有重复键,则必须使用where子句处理重复键。

var result = dictionary1.Union(dictionary2.Where(k => !dictionary1.ContainsKey(k.Key))).ToDictionary(k => k.Key, v => v.Value)

注意:它不会得到重复的密钥。如果有重复的密钥,则它将获得Dictionary1的密钥。

选项3:如果要使用ToLookup。那么您将获得一个查询,该查询每个键可以具有多个值。您可以将该查询转换为字典:

var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());

6

如何添加params过载?

另外,您应该键入它们以IDictionary获取最大的灵活性。

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(IEnumerable<IDictionary<TKey, TValue>> dictionaries)
{
    // ...
}

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(params IDictionary<TKey, TValue>[] dictionaries)
{
    return Merge((IEnumerable<TKey, TValue>) dictionaries);
}

4
他注意到您可以使DictionaryExtensions类更好,从而在这里添加了其他答案。也许这个问题应该成为Wiki或git ...中注册的类。
克里斯·莫斯基尼

5

考虑到字典键查找和删除性能,因为它们是散列操作,并且考虑到问题的措辞是最好的方法,我认为下面是一种完全有效的方法,而其他方法则有些复杂,恕我直言。

    public static void MergeOverwrite<T1, T2>(this IDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null) return;

        foreach (var e in newElements)
        {
            dictionary.Remove(e.Key); //or if you don't want to overwrite do (if !.Contains()
            dictionary.Add(e);
        }
    }

或者,如果您在多线程应用程序中工作,并且您的字典无论如何都必须是线程安全的,则应该这样做:

    public static void MergeOverwrite<T1, T2>(this ConcurrentDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null || newElements.Count == 0) return;

        foreach (var ne in newElements)
        {
            dictionary.AddOrUpdate(ne.Key, ne.Value, (key, value) => value);
        }
    }

然后,您可以包装它以使其处理字典的枚举。无论如何,您正在看的大约是O(3n)(所有条件都是完美的),因为幕后.Add()将进行额外的,不必要的但实际上是免费Contains()的。我认为情况不会好得多。

如果要限制对大型集合的额外操作,则应总结Count将要合并的每个词典的,并将目标词典的容量设置为该容量,从而避免了以后调整大小的成本。因此,最终产品就是这样...

    public static IDictionary<T1, T2> MergeAllOverwrite<T1, T2>(IList<IDictionary<T1, T2>> allDictionaries)
    {
        var initSize = allDictionaries.Sum(d => d.Count);
        var resultDictionary = new Dictionary<T1, T2>(initSize);
        allDictionaries.ForEach(resultDictionary.MergeOverwrite);
        return resultDictionary;
    }

请注意,我采用了IList<T>这种方法...主要是因为,如果采用IEnumerable<T>,您将打开多个相同集合的枚举,如果您从递归的LINQ中获取字典集合,则可能会非常昂贵声明。


4

基于上面的答案,但添加了一个Func参数以使调用方可以处理重复项:

public static Dictionary<TKey, TValue> Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> dicts, 
                                                           Func<IGrouping<TKey, TValue>, TValue> resolveDuplicates)
{
    if (resolveDuplicates == null)
        resolveDuplicates = new Func<IGrouping<TKey, TValue>, TValue>(group => group.First());

    return dicts.SelectMany<Dictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(dict => dict)
                .ToLookup(pair => pair.Key, pair => pair.Value)
                .ToDictionary(group => group.Key, group => resolveDuplicates(group));
}

4

该聚会现在几乎已经死了,但是这是user166390的“改进”版本,已进入我的扩展库。除了一些细节之外,我还添加了一个代理来计算合并值。

/// <summary>
/// Merges a dictionary against an array of other dictionaries.
/// </summary>
/// <typeparam name="TResult">The type of the resulting dictionary.</typeparam>
/// <typeparam name="TKey">The type of the key in the resulting dictionary.</typeparam>
/// <typeparam name="TValue">The type of the value in the resulting dictionary.</typeparam>
/// <param name="source">The source dictionary.</param>
/// <param name="mergeBehavior">A delegate returning the merged value. (Parameters in order: The current key, The current value, The previous value)</param>
/// <param name="mergers">Dictionaries to merge against.</param>
/// <returns>The merged dictionary.</returns>
public static TResult MergeLeft<TResult, TKey, TValue>(
    this TResult source,
    Func<TKey, TValue, TValue, TValue> mergeBehavior,
    params IDictionary<TKey, TValue>[] mergers)
    where TResult : IDictionary<TKey, TValue>, new()
{
    var result = new TResult();
    var sources = new List<IDictionary<TKey, TValue>> { source }
        .Concat(mergers);

    foreach (var kv in sources.SelectMany(src => src))
    {
        TValue previousValue;
        result.TryGetValue(kv.Key, out previousValue);
        result[kv.Key] = mergeBehavior(kv.Key, kv.Value, previousValue);
    }

    return result;
}

2

@Tim:应该是注释,但是注释不允许进行代码编辑。

Dictionary<string, string> t1 = new Dictionary<string, string>();
t1.Add("a", "aaa");
Dictionary<string, string> t2 = new Dictionary<string, string>();
t2.Add("b", "bee");
Dictionary<string, string> t3 = new Dictionary<string, string>();
t3.Add("c", "cee");
t3.Add("d", "dee");
t3.Add("b", "bee");
Dictionary<string, string> merged = t1.MergeLeft(t2, t2, t3);

注意:我将@ANeves的修改应用于@Andrew Orsich的解决方案,因此MergeLeft现在看起来像这样:

public static Dictionary<K, V> MergeLeft<K, V>(this Dictionary<K, V> me, params IDictionary<K, V>[] others)
    {
        var newMap = new Dictionary<K, V>(me, me.Comparer);
        foreach (IDictionary<K, V> src in
            (new List<IDictionary<K, V>> { me }).Concat(others))
        {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K, V> p in src)
            {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

接近我自己写的东西。这,ofc,仅适用于Dictionary类型。可以为其他Dictionary类型(ConcurrentDictionaryReadOnlyDictionary等)添加重载,我认为无需创建新列表,因此concat本身是必要的。我只是首先迭代params数组,然后迭代每个字典的每个KVP。我没有退缩。
2015年

您可以使用IDictionary而不是Dictionary
Mariusz Jamro

Dictionary即使您在上使用了extension方法,也总是会得到一个对象ConcurrentDictionary。这可能导致难以跟踪的错误。
马塞尔

2

我知道这是一个老问题,但是由于我们现在有了LINQ,因此您可以像这样在一行中完成它

Dictionary<T1,T2> merged;
Dictionary<T1,T2> mergee;
mergee.ToList().ForEach(kvp => merged.Add(kvp.Key, kvp.Value));

要么

mergee.ToList().ForEach(kvp => merged.Append(kvp));

对不起@Cruces,但您的第一个示例重复了stackoverflow.com/a/6695211/704808的答案,而第二个示例只是在追加了每个项目之后一直丢弃扩展的IEnumerable <KeyValuePair <string,string >> mergee

2

害怕看到复杂的答案,这是C#的新手。

这里有一些简单的答案。
合并d1,d2等字典,并处理所有重叠的键(以下示例中的“ b”):

例子1

{
    // 2 dictionaries,  "b" key is common with different values

    var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
    var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };

    var result1 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
    // result1 is  a=10, b=21, c=30    That is, took the "b" value of the first dictionary

    var result2 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
    // result2 is  a=10, b=22, c=30    That is, took the "b" value of the last dictionary
}

例子2

{
    // 3 dictionaries,  "b" key is common with different values

    var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
    var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };
    var d3 = new Dictionary<string, int>() { { "d", 40 }, { "b", 23 } };

    var result1 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
    // result1 is  a=10, b=21, c=30, d=40    That is, took the "b" value of the first dictionary

    var result2 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
    // result2 is  a=10, b=23, c=30, d=40    That is, took the "b" value of the last dictionary
}

有关更复杂的方案,请参见其他答案。
希望能有所帮助。


2
using System.Collections.Generic;
using System.Linq;

public static class DictionaryExtensions
{
    public enum MergeKind { SkipDuplicates, OverwriteDuplicates }
    public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, MergeKind kind = MergeKind.SkipDuplicates) =>
        source.ToList().ForEach(_ => { if (kind == MergeKind.OverwriteDuplicates || !target.ContainsKey(_.Key)) target[_.Key] = _.Value; });
}

您可以跳过/忽略(默认)或覆盖重复项:并且Bob是您的叔叔,前提是您对Linq的性能不是很挑剔,但是像我一样喜欢简洁的可维护代码:在这种情况下,您可以删除默认的MergeKind.SkipDuplicates以强制执行给调用者一个选择,并使开发人员意识到结果是什么!


下面的改进答案没有必要的二进制枚举,当bool会做...
mattjs

2

请注意,如果使用扩展方法“添加”,则可以使用集合初始值设定项来组合所需的字典,如下所示:

public static void Add<K, V>(this Dictionary<K, V> d, Dictionary<K, V> other) {
  foreach (var kvp in other)
  {
    if (!d.ContainsKey(kvp.Key))
    {
      d.Add(kvp.Key, kvp.Value);
    }
  }
}


var s0 = new Dictionary<string, string> {
  { "A", "X"}
};
var s1 = new Dictionary<string, string> {
  { "A", "X" },
  { "B", "Y" }
};
// Combine as many dictionaries and key pairs as needed
var a = new Dictionary<string, string> {
  s0, s1, s0, s1, s1, { "C", "Z" }
};

1

使用扩展方法合并。当有重复的键时,它不会引发异常,而是用第二个字典中的键替换那些键。

internal static class DictionaryExtensions
{
    public static Dictionary<T1, T2> Merge<T1, T2>(this Dictionary<T1, T2> first, Dictionary<T1, T2> second)
    {
        if (first == null) throw new ArgumentNullException("first");
        if (second == null) throw new ArgumentNullException("second");

        var merged = new Dictionary<T1, T2>();
        first.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
        second.ToList().ForEach(kv => merged[kv.Key] = kv.Value);

        return merged;
    }
}

用法:

Dictionary<string, string> merged = first.Merge(second);

1

与我之前的回答相比,简化了使用方式,如果存在,则默认为无损合并,如果为true,则完全覆盖,而不是使用枚举。它仍然可以满足我自己的需求,不需要任何高级代码:

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

public static partial class Extensions
{
    public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, bool overwrite = false)
    {
        source.ToList().ForEach(_ => {
            if ((!target.ContainsKey(_.Key)) || overwrite)
                target[_.Key] = _.Value;
        });
    }
}

0

使用进行合并以EqualityComparer将要比较的项目映射到其他值/类型。在这里,我们将从KeyValuePair(枚举字典时的项目类型)映射到Key

public class MappedEqualityComparer<T,U> : EqualityComparer<T>
{
    Func<T,U> _map;

    public MappedEqualityComparer(Func<T,U> map)
    {
        _map = map;
    }

    public override bool Equals(T x, T y)
    {
        return EqualityComparer<U>.Default.Equals(_map(x), _map(y));
    }

    public override int GetHashCode(T obj)
    {
        return _map(obj).GetHashCode();
    }
}

用法:

// if dictA and dictB are of type Dictionary<int,string>
var dict = dictA.Concat(dictB)
                .Distinct(new MappedEqualityComparer<KeyValuePair<int,string>,int>(item => item.Key))
                .ToDictionary(item => item.Key, item=> item.Value);

0

要么 :

public static IDictionary<TKey, TValue> Merge<TKey, TValue>( IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        return x
            .Except(x.Join(y, z => z.Key, z => z.Key, (a, b) => a))
            .Concat(y)
            .ToDictionary(z => z.Key, z => z.Value);
    }

结果是一个联合,其中重复项“ y”获胜。


0
public static IDictionary<K, V> AddRange<K, V>(this IDictionary<K, V> one, IDictionary<K, V> two)
        {
            foreach (var kvp in two)
            {
                if (one.ContainsKey(kvp.Key))
                    one[kvp.Key] = two[kvp.Key];
                else
                    one.Add(kvp.Key, kvp.Value);
            }
            return one;
        }

0

@ user166390答案的版本带有添加的IEqualityComparer参数,以允许不区分大小写的键比较。

    public static T MergeLeft<T, K, V>(this T me, params Dictionary<K, V>[] others)
        where T : Dictionary<K, V>, new()
    {
        return me.MergeLeft(me.Comparer, others);
    }

    public static T MergeLeft<T, K, V>(this T me, IEqualityComparer<K> comparer, params Dictionary<K, V>[] others)
        where T : Dictionary<K, V>, new()
    {
        T newMap = Activator.CreateInstance(typeof(T), new object[] { comparer }) as T;

        foreach (Dictionary<K, V> src in 
            (new List<Dictionary<K, V>> { me }).Concat(others))
        {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K, V> p in src)
            {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

0
fromDic.ToList().ForEach(x =>
        {
            if (toDic.ContainsKey(x.Key))
                toDic.Remove(x.Key);
            toDic.Add(x);
        });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.