可変のLookup
- 2009-08-13
一対多の Dictionary が欲しい - present
今だと、やっぱLookupがあるので、ILookupの実装という形で作った方が統一感取れていいのかな……? 通常のLookupはToLookupでしか作成できず不変なので、追加したり削除したり出来るILookupの実装を作りました。MultiDictionaryとかMultiMapとか言われているもの、ですが名前をimmutableの反対なのでMutableLookupとしてみました。かなり、微妙。名前ってむつかしい。んで、中身はDictionary<TKey, List<TValue>>のゆるふわラッピング。コンストラクタでIEqualityComparerを突っ込めるので、大文字小文字無視とかも出来ます。
// こんな風に使います
var mutableLookup = new MutableLookup<string, string>();
mutableLookup.Add("食べ物", "たこやき");
mutableLookup.AddRange("食べ物", new[] { "いかやき", "さかなやき" });
mutableLookup.Add("飲み物", "ぽかり");
IEnumerable<string> tabemono = mutableLookup["食べ物"]; // インデクサでアクセス
foreach (var item in mutableLookup) // 列挙するとIGroupingが出てくる
{
var key = item.Key; // キー(食べ物,飲み物)
var array = item.ToArray(); // 配列
}
public class MutableLookup<TKey, TValue> : ILookup<TKey, TValue>
{
private readonly Dictionary<TKey, List<TValue>> dictionary;
// Constructor
public MutableLookup()
{
dictionary = new Dictionary<TKey, List<TValue>>();
}
public MutableLookup(IEqualityComparer<TKey> keyComparer)
{
dictionary = new Dictionary<TKey, List<TValue>>(keyComparer);
}
// Property
public IEnumerable<TKey> Keys
{
get { return dictionary.Select(kvp => kvp.Key); }
}
public IEnumerable<TValue> Values
{
get { return dictionary.SelectMany(kvp => kvp.Value); }
}
// Methods
public ILookup<TKey, TValue> AsReadOnly()
{
return dictionary.SelectMany(kvp => kvp.Value, (kvp, Value) => new { kvp.Key, Value })
.ToLookup(t => t.Key, t => t.Value);
}
public void Add(TKey key, TValue value)
{
if (dictionary.ContainsKey(key)) dictionary[key].Add(value);
else dictionary.Add(key, new List<TValue> { value });
}
public void AddRange(TKey key, IEnumerable<TValue> values)
{
if (dictionary.ContainsKey(key)) dictionary[key].AddRange(values);
else dictionary.Add(key, new List<TValue>(values));
}
public void RemoveKey(TKey key)
{
dictionary.Remove(key);
}
public void RemoveValue(TKey key, TValue value)
{
if (!dictionary.ContainsKey(key)) return;
var list = dictionary[key];
list.Remove(value);
if (!list.Any()) dictionary.Remove(key);
}
public void RemoveWhere(TKey key, Func<TValue, bool> predicate)
{
if (!dictionary.ContainsKey(key)) return;
var list = dictionary[key];
list.RemoveAll(new Predicate<TValue>(predicate));
if (!list.Any()) dictionary.Remove(key);
}
public void Clear()
{
dictionary.Clear();
}
public bool Contains(TKey key, TValue value)
{
if (dictionary.ContainsKey(key))
{
return dictionary[key].Contains(value);
}
return false;
}
#region ILookup<TKey,TValue>
public bool Contains(TKey key)
{
return dictionary.ContainsKey(key);
}
public int Count
{
get { return dictionary.Count; }
}
public IEnumerable<TValue> this[TKey key]
{
get
{
return (dictionary.ContainsKey(key))
? dictionary[key].AsEnumerable()
: Enumerable.Empty<TValue>();
}
}
#endregion
#region IEnumerable<IGrouping<TKey,TValue>>
public IEnumerator<IGrouping<TKey, TValue>> GetEnumerator()
{
return dictionary
.Select(kvp => new Grouping(kvp.Key, kvp.Value))
.Cast<IGrouping<TKey, TValue>>()
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
// Nested Types
private class Grouping : IGrouping<TKey, TValue>
{
private TKey key;
private List<TValue> list;
public Grouping(TKey key, List<TValue> list)
{
this.key = key;
this.list = list;
}
#region IGrouping<TKey,TValue>
public TKey Key
{
get { return key; }
}
#endregion
#region IEnumerable<TValue>
public IEnumerator<TValue> GetEnumerator()
{
return list.GetEnumerator();
}
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
}
利用はご自由にどうぞ。パブリックドメイン、でいいのかな? あー、AsReadOnlyは新しいのに作り替えちゃってるのでAsじゃないですねえ。まあ、放置。あと、new Predicate()が果てしなくダサい。これ何とかならないのかな。delegateはFuncとAction以外は消滅しちゃえばいいのに。ジェネリクス以前のコレクションクラスと同じで、3.0以前の負の遺産だと何となく思ってる。