可変のLookup

一対多の 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以前の負の遺産だと何となく思ってる。

Comment (0)

Name
WebSite(option)
Comment

Trackback(0) | http://neue.cc/2009/08/13_185.html/trackback

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Developer Technologies(C#)

April 2011
|
July 2021

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc