//
// Copyright (c) 2010-2018 Antmicro
// Copyright (c) 2011-2015 Realtime Embedded
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System;
using System.Collections.Generic;
using System.Linq;
namespace Antmicro.Renode.Utilities.Collections
{
///
/// This is an implementation of MultiValueDictionary which is memory effictient at expense of sligthly
/// more logic. It holds key-value associations for singular values in plain dictionary, and moves them to
/// a key-list of values dictionary when more than one value gets associated to the same key.
///
/// This way, the overhead of keeping lists is limited only to keys that actually need it.
///
/// Semantically it behaves as Dictionary>. So Get methods
/// always return a collection, even in cases when there are no elements or one element to return.
///
public class MultiValueDictionary : IEnumerable where TValue : IEquatable
{
public MultiValueDictionary()
{
multiple = new Dictionary>();
single = new Dictionary();
}
public IEnumerator GetEnumerator()
{
return single.Values.Concat(multiple.Values.SelectMany(list => list)).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(TKey key, TValue value)
{
TValue existingSingle;
if(single.TryGetValue(key, out existingSingle))
{
single.Remove(key);
multiple.Add(key, new List{ existingSingle, value });
return;
}
List existingMultiple;
if(multiple.TryGetValue(key, out existingMultiple))
{
existingMultiple.Add(value);
return;
}
single.Add(key, value);
}
public void AddRange(TKey key, IEnumerable values)
{
TValue existingSingle;
if(single.TryGetValue(key, out existingSingle))
{
single.Remove(key);
var list = new List{ existingSingle };
list.AddRange(values);
multiple.Add(key, list);
return;
}
List existingMultiple;
if(multiple.TryGetValue(key, out existingMultiple))
{
existingMultiple.AddRange(values);
}
else
{
multiple.Add(key, new List(values));
}
}
///
/// Removes all values associated with the key.
///
/// Key.
public bool Remove(TKey key)
{
return single.Remove(key) || multiple.Remove(key);
}
///
/// Removes particular key, value association.
///
/// Key.
/// Value.
public bool Remove(TKey key, TValue value)
{
TValue existingSingle;
if(single.TryGetValue(key, out existingSingle) && existingSingle.Equals(value))
{
return single.Remove(key);
}
List existingMultipleValues;
if(multiple.TryGetValue(key, out existingMultipleValues) && existingMultipleValues.Remove(value))
{
//if there is one element left in list, move it to the single values
if(existingMultipleValues.Count == 1)
{
single.Add(key, existingMultipleValues.First());
multiple.Remove(key);
}
}
return true;
}
///
/// Tries to get the collection of values associated with the key.
///
/// true, if collection is not empty, false otherwise.
/// Key.
/// Values.
public bool TryGetValue(TKey key, out IReadOnlyCollection values)
{
TValue existingSingle;
if(single.TryGetValue(key, out existingSingle))
{
values = new List { existingSingle };
return true;
}
List existingMultiple;
var ret = multiple.TryGetValue(key, out existingMultiple);
if(ret)
{
values = existingMultiple.AsReadOnly();
}
else
{
values = new List();
}
return ret;
}
public bool ContainsKey(TKey key)
{
return single.ContainsKey(key) || multiple.ContainsKey(key);
}
public bool ContainsValue(TValue value)
{
return this.Contains(value);
}
public bool Contains(TKey key)
{
return ContainsKey(key);
}
public bool Contains(TKey key, TValue value)
{
IReadOnlyCollection values;
return TryGetValue(key, out values) && values.Contains(value);
}
public void Clear()
{
single.Clear();
multiple.Clear();
}
///
/// Contains dictionary of keys associated with one value.
///
private readonly Dictionary single;
///
/// Contains dictionary of keys associated with more than one value.
///
private readonly Dictionary> multiple;
}
}