4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[C#] 双方向辞書を自作する

Last updated at Posted at 2020-07-26

##前書き

Dictionary<TKey, TValue>は,KeyからValueへの単方向辞書である.例えば図鑑番号をKey,種族名をValueとしたポ〇モン図鑑Dictionary<int, string>では,Key = 1に対して"不思議種"というデータを$O(1)$操作で取得可能あるが,逆に種族名から図鑑番号を探索したいときは,$O(n)$操作の線形探索を行うしかない.今回は,KeyからValueへのマッピングと同時に,その逆のValueからKeyへのマッピングも保証する双方向辞書LinkedDictionary<TKey, TValue>の作成を考える.

##使用例

  • 以下は,双方向辞書LinkedDictionary<int, string>の使用例である.
  • Key<int>からValue<string>へのアクセスと,Value<string>からKey<int>へのアクセスを$O(1)$操作で実行可能である.
var pokomonLibrary = new LinkedDictionary<int, string>();
pokomonLibrary.Add(0, "ィ゛ゃゾ┛A");
pokomonLibrary.Add(6, "アネ゙デパミ");

var pokomon1 = pokomonLibrary[0];               // "ィ゛ゃゾ┛A"
var pokomon2 = pokomonLibrary["ィ゛ゃゾ┛A"];     // 0
var pokomon3 = pokomonLibrary[6];               // "アネ゙デパミ"
var pokomon4 = pokomonLibrary["アネ゙デパミ"];    // 6

##実装

クラスのの全容は GitHub へ公開した.
https://github.com/Takuto168/Takuto168park/blob/master/LinkedDictionary.cs

以下に,その機能一覧と実装方法を示す.

###クラスの作成

  • ジェネリッククラスLinkedDictionary<TKey, TValue>を作成し,IDictionary<TKey, TValue>インターフェースを継承する.
  • キーから値へのマッピングを保証するためのDictionary<TKey, TValue>フィールドと,値からキーへのマッピングを保証するためのDictionary<TValue, TKey>フィールドを用意する.以降,これらを内部辞書と呼ぶ.
LinkedDictionary.cs
/// <summary>
/// 双方向辞書
/// キーから値へのマッピングと,その逆の値からキーへのマッピングを同時に保証するキーと値のコレクションを表します.
/// </summary>
/// <typeparam name="TKey">ディクショナリ内のキーの型.</typeparam>
/// <typeparam name="TValue">ディクショナリ内の値の型.</typeparam>
public class LinkedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    /// <summary>
    /// キーから値へのマッピング.
    /// </summary>
    private Dictionary<TKey, TValue> _keyToValues;
    
    /// <summary>
    /// 値からキーへのマッピング.
    /// </summary>
    private Dictionary<TValue, TKey> _valueToKeys;
}

###コンストラクタ

  • Dictionary<TKey, TValue>のコンストラクタに倣い,下記の条件を指定できる幾つかのパターンのコンストラクタを定義する.
    • 空のLinkedDictionary<TKey, TValue>を作成
    • 容量Capacityを明示的に指定して作成
    • キーの型の既定の等値比較子IEqualityComparer<TKey>を指定して作成
    • 値の型の既定の等値比較子IEqualityComparer<TValue>を指定して作成
    • IDictionary<TKey,TValue>から要素をコピーして作成
    • IEnumerable<KeyValuePair<TKey, TValue>>から要素をコピーして作成
#region->constructer
/// <summary>
/// 空で,既定の初期量を備え,キーの型と値型の既定の等値比較子を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
public LinkedDictionary() : this(0, null, null) { }

/// <summary>
/// 空で,指定した初期量を備え,キーの型と値型の既定の等値比較子を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="capacity">LinkedDictionary<TKey,TValue> が格納できる要素数の初期値.</param>
public LinkedDictionary(int capacity) : this(capacity, null, null) { }

/// <summary>
/// 空で,既定の初期量を備え,指定した IEqualityComparer<TKey> を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="comparerKey">キーの比較時に使用する IEqualityComparer<TKey> 実装.キーの型の既定の EqualityComparer<TKey> を使用する場合は null.</param>
public LinkedDictionary(IEqualityComparer<TKey> comparerKey) : this(0, comparerKey, null) { }

/// <summary>
/// 空で,既定の初期量を備え,指定した IEqualityComparer<TValue> を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="comparerValue">値の比較時に使用する IEqualityComparer<TValue> 実装.値の型の既定の EqualityComparer<TValue> を使用する場合は null.</param>
public LinkedDictionary(IEqualityComparer<TValue> comparerValue) : this(0, null, comparerValue) { }

/// <summary>
/// 空で,既定の初期量を備え,指定した IEqualityComparer<TKey> と IEqualityComparer<TValue> を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="comparerKey">キーの比較時に使用する IEqualityComparer<TKey> 実装.キーの型の既定の EqualityComparer<TKey> を使用する場合は null.</param>
/// <param name="comparerValue">値の比較時に使用する IEqualityComparer<TValue> 実装.値の型の既定の EqualityComparer<TValue> を使用する場合は null.</param>
public LinkedDictionary(IEqualityComparer<TKey> comparerKey, IEqualityComparer<TValue> comparerValue) : this(0, comparerKey, comparerValue) { }

/// <summary>
/// 空で,指定したの初期量を備え,指定した IEqualityComparer<TKey> と IEqualityComparer<TValue> を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="capacity">LinkedDictionary<TKey,TValue> が格納できる要素数の初期値.</param>
/// <param name="comparerKey">キーの比較時に使用する IEqualityComparer<TKey> 実装.キーの型の既定の EqualityComparer<TKey> を使用する場合は null.</param>
/// <param name="comparerValue">値の比較時に使用する IEqualityComparer<TValue> 実装.値の型の既定の EqualityComparer<TValue> を使用する場合は null.</param>
public LinkedDictionary(int capacity, IEqualityComparer<TKey> comparerKey, IEqualityComparer<TValue> comparerValue)
{
    if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity));

    this._keyToValues = new Dictionary<TKey, TValue>(capacity, comparerKey);
    this._valueToKeys = new Dictionary<TValue, TKey>(capacity, comparerValue);
}

/// <summary>
/// 指定した IDictionary<TKey,TValue> から要素をコピーして格納し,キー型と値型の既定の等値比較子を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="dictionary">新しい LinkedDictionary<TKey,TValue> に要素をコピーする IDictionary<TKey,TValue>.</param>
public LinkedDictionary(IDictionary<TKey, TValue> dictionary) : this(dictionary, null, null) { }

/// <summary>
/// 指定した IDictionary<TKey,TValue> から要素をコピーして格納し,指定した IEqualityComparer<TKey> を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="dictionary">新しい LinkedDictionary<TKey,TValue> に要素をコピーする IDictionary<TKey,TValue>.</param>
/// <param name="comparerKey">キーの比較時に使用する IEqualityComparer<TKey> 実装.キーの型の既定の EqualityComparer<TKey> を使用する場合は null.</param>
public LinkedDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparerKey) : this(dictionary, comparerKey, null) { }

/// <summary>
/// 指定した IDictionary<TKey,TValue> から要素をコピーして格納し,指定した IEqualityComparer<TValue> を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="dictionary">新しい LinkedDictionary<TKey,TValue> に要素をコピーする IDictionary<TKey,TValue>.</param>
/// <param name="comparerValue">値の比較時に使用する IEqualityComparer<TValue> 実装.値の型の既定の EqualityComparer<TValue> を使用する場合は null.</param>
public LinkedDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TValue> comparerValue) : this(dictionary, null, comparerValue) { }

/// <summary>
/// 指定した IDictionary<TKey,TValue> から要素をコピーして格納し,指定した IEqualityComparer<TKey> と IEqualityComparer<TValue> を使用する,LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="dictionary">新しい LinkedDictionary<TKey,TValue> に要素をコピーする IDictionary<TKey,TValue>.</param>
/// <param name="comparerKey">キーの比較時に使用する IEqualityComparer<TKey> 実装.キーの型の既定の EqualityComparer<TKey> を使用する場合は null.</param>
/// <param name="comparerValue">値の比較時に使用する IEqualityComparer<TValue> 実装.値の型の既定の EqualityComparer<TValue> を使用する場合は null.</param>
public LinkedDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparerKey, IEqualityComparer<TValue> comparerValue) :
        this(dictionary != null ? dictionary.Count : 0, comparerKey, comparerValue)
{
    if (dictionary == null) throw new ArgumentNullException(nameof(dictionary));
    this.AddRange(dictionary);
}

/// <summary>
/// 指定した IEnumerable<T> からコピーされた要素を格納する LinkedDictionary<TKey,TValue> クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="collection">新しい LinkedDictionary<TKey,TValue> に要素をコピーする Enumerable<KeyValuePair<TKey,TValue>>.</param>
public LinkedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, null, null) { }

/// <summary>
/// 指定した IEnumerable<KeyValuePair<TKey,TValue>> から要素をコピーして格納し,指定した IEqualityComparer<TKey> を使用する,LinkedDictionary<TKey, TValue>クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="collection">新しい LinkedDictionary<TKey,TValue> に要素をコピーする Enumerable<KeyValuePair<TKey,TValue>>.</param>
/// <param name="comparerKey">キーの比較時に使用する IEqualityComparer<TKey> 実装.キーの型の既定の EqualityComparer<TKey> を使用する場合は null.</param>
public LinkedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparerKey) : this(collection, comparerKey, null) { }

/// <summary>
/// 指定した IEnumerable<KeyValuePair<TKey,TValue>> から要素をコピーして格納し,指定した IEqualityComparer<TValue> を使用する,LinkedDictionary<TKey, TValue>クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="collection">新しい LinkedDictionary<TKey,TValue> に要素をコピーする Enumerable<KeyValuePair<TKey,TValue>>.</param>
/// <param name="comparerValue">値の比較時に使用する IEqualityComparer<TValue> 実装.値の型の既定の EqualityComparer<TValue> を使用する場合は null.</param>
public LinkedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TValue> comparerValue) : this(collection, null, comparerValue) { }

/// <summary>
/// 指定した IEnumerable<KeyValuePair<TKey,TValue>> から要素をコピーして格納し,指定した IEqualityComparer<TKey> と IEqualityComparer<TValue> を使用する,LinkedDictionary<TKey, TValue>クラスの新しいインスタンスを初期化します.
/// </summary>
/// <param name="collection">新しい LinkedDictionary<TKey,TValue> に要素をコピーする Enumerable<KeyValuePair<TKey,TValue>>.</param>
/// <param name="comparerKey">キーの比較時に使用する IEqualityComparer<TKey> 実装.キーの型の既定の EqualityComparer<TKey> を使用する場合は null.</param>
/// <param name="comparerValue">値の比較時に使用する IEqualityComparer<TValue> 実装.値の型の既定の EqualityComparer<TValue> を使用する場合は null.</param>
public LinkedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparerKey, IEqualityComparer<TValue> comparerValue) :
this(collection != null ? collection.Count() : 0, comparerKey, comparerValue)
{
    if (collection == null) throw new ArgumentNullException(nameof(collection));
    this.AddRange(collection);
}

###プロパティ

  • 明示的なインターフェースの実装では,基本的に内部辞書のプロパティを参照する.
  • 通常のTValue Item[TKey key]プロパティに加え,逆探知を行うためのTKey Item[TValue value]プロパティを用意する.ここで,setのときは,一方の内部辞書のValueを書き換えるとともに,他方のKeyを書き換える必要があることに留意する.
#region->property
/// <summary>
/// ディクショナリのキーが等しいかどうかを確認するために使用する IEqualityComparer<TKey> を取得します.
/// </summary>
public IEqualityComparer<TKey> ComparerKey => this._keyToValues.Comparer;

/// <summary>
/// ディクショナリの値が等しいかどうかを確認するために使用する IEqualityComparer<TValue> を取得します.
/// </summary>
public IEqualityComparer<TValue> ComparerValue => this._valueToKeys.Comparer;

/// <summary>
/// LinkedDictionary<TKey,TValue> に格納されているキー/値ペアの数を取得します.
/// </summary>
public int Count => this._keyToValues.Count;

/// <summary>
/// 指定されたキーに関連付けられた値を取得または設定します.
/// </summary>
/// <param name="key">取得または設定する値のキー.</param>
public TValue this[TKey key]
{
    get => this.GetValue(key);
    set => this.SetValue(key, value);
}

/// <summary>
/// 指定された値に関連付けられたキーを取得または設定します.
/// </summary>
/// <param name="argValue">取得または設定するキーの値.</param>
public TKey this[TValue argValue]
{
    get => this.GetKey(argValue);
    set => this.SetKey(argValue, value);
}

/// <summary>
/// LinkedDictionary<TKey,TValue> 内のキーを格納しているコレクションを取得します.
/// </summary>
public ICollection<TKey> Keys => this._keyToValues.Keys;

/// <summary>
/// LinkedDictionary<TKey,TValue> 内の値を格納しているコレクションを取得します.
/// </summary>
public ICollection<TValue> Values => this._keyToValues.Values;

/// <summary>
/// IDictionary が読み取り専用かどうかを示す値を取得します.
/// </summary>
public bool IsReadOnly => false;

/// <summary>
/// LinkedDictionary<TKey,TValue> 内のキー/値ペアのコレクションを取得します.
/// </summary>
public IEnumerable<KeyValuePair<TKey, TValue>> KeyValuePairs
{
    get
    {
        foreach (var item in this._keyToValues) yield return item;
    }
}

###メソッド

  • 明示的なインターフェースの実装を含めたDictionary<TKey,TValue>の実装機能に加え,いくつかの独自メソッドを作成した.
  • 独自メソッドの考案に際しては,Qiitaの記事よりDictionaryの拡張メソッド 36選を参考にさせて頂いた.

キー,値の取得

  • キーと値の取得に関するいくつかのメソッドを定義する.
#region->method->get
/// <summary>
/// 指定されたキーに関連付けられた値を取得します.
/// </summary>
/// <param name="key">取得または設定する値のキー.</param>
public TValue GetValue(TKey key)
{
    if (this.TryGetValue(key, out var value)) return value;
    throw new KeyNotFoundException();
}

/// <summary>
/// 指定された値に関連付けられたキーを取得します.
/// </summary>
/// <param name="value">取得または設定するキーの値.</param>
public TKey GetKey(TValue value)
{
    if (this.TryGetKey(value, out var key)) return key;
    throw new KeyNotFoundException();
}

/// <summary>
/// 指定されたキーに関連付けられた値の取得を試みます.
/// </summary>
/// <param name="key">取得する値のキー.</param>
/// <param name="value">キーが見つかった場合は,指定したキーに関連付けられている値が格納されます.それ以外の場合は value パラメーターの型に対する既定の値.</param>
/// <returns>指定されたキーを持つ要素が LinkedDictionary<TKey,TValue> に含まれている場合は true,含まれていない場合は false.</returns>
public bool TryGetValue(TKey key, out TValue value) => this._keyToValues.TryGetValue(key, out value);

/// <summary>
/// 指定された値に関連付けられたキーの取得をを試みます.
/// </summary>
/// <param name="value">取得するキーの値.</param>
/// <param name="key">値が見つかった場合は,指定した値に関連付けられているキーが格納されます.それ以外の場合は key パラメーターの型に対する既定の値.</param>
/// <returns>指定された値を持つ要素が LinkedDictionary<TKey,TValue> に含まれている場合は true,含まれていない場合は false.</returns>
public bool TryGetKey(TValue value, out TKey key) => this._valueToKeys.TryGetValue(value, out key);

/// <summary>
/// 指定されたキーに関連付けられた値を取得します.
/// </summary>
/// <param name="key">取得する値のキー.</param>
/// <returns>値が見つかった場合は,指定した値に関連付けられているキー.それ以外の場合は value パラメーターの型に対する既定の値.</returns>
public TValue GetValueOrDefault(TKey key) => this.TryGetValue(key, out var value) ? value : default(TValue);

/// <summary>
/// 指定された値に関連付けられたキーを取得します.
/// </summary>
/// <param name="value">取得するキーの値.</param>
/// <returns>キーが見つかった場合は,指定した値に関連付けられている値.それ以外の場合は key パラメーターの型に対する既定の値.</returns>
public TKey GetKeyOrDefault(TValue value) => this.TryGetKey(value, out var key) ? key : default(TKey);

/// <summary>
/// 指定されたキーに関連付けられた値を取得し,値が見つからなかった場合は指定した value を追加します.
/// </summary>
/// <param name="key">取得する値のキー.</param>
/// <param name="value">値が見つからなかった場合に追加する値.</param>
/// <returns>値が見つかった場合は指定されたキーに関連付けられた値.見つからなかった場合は追加した値.</returns>
public TValue GetValueOrAdd(TKey key, TValue value)
{
    this.TryAdd(key, value);
    return this._keyToValues[key];
}

/// <summary>
/// 指定された値に関連付けられたキーを取得し,キーが見つからなかった場合は指定した key を追加します.
/// </summary>
/// <param name="value">取得するキーの値.</param>
/// <param name="key">キーが見つからなかった場合に追加するキー.</param>
/// <returns>キーが見つかった場合は指定された値に関連付けられたキー.見つからなかった場合は追加したキー.</returns>
public TKey GetKeyOrAdd(TValue value, TKey key)
{
    this.TryAdd(key, value);
    return this._valueToKeys[value];
}

/// <summary>
/// 指定されたキーに関連付けられた値を取得し,値が見つからなかった場合は指定した TValue 型に対する既定値を追加します.
/// </summary>
/// <param name="key">取得する値のキー.</param>
/// <returns>値が見つかった場合は指定されたキーに関連付けられた値.見つからなかった場合は TValue 型の既定値.</returns>
public TValue GetValueOrAddDefault(TKey key) => this.GetValueOrAdd(key, default(TValue));

/// <summary>
/// 指定された値に関連付けられたキーを取得し,キーが見つからなかった場合は指定した TKey 型に対する既定値を追加します.
/// </summary>
/// <param name="value">取得するキーの値.</param>
/// <returns>キーが見つかった場合は指定された値に関連付けられたキー.見つからなかった場合は TKey 型の既定値.</returns>
public TKey GetKeyOrAddDefault(TValue value) => this.GetKeyOrAdd(value, default(TKey));

キー,値の設定

  • キーと値の設定に関するいくつかのメソッドを定義する.
  • キーと値の設定では,一方の内部辞書のValueを書き換えるとともに,他方のKeyを書き換える必要があることに留意する.
#region->method->set
/// <summary>
/// 指定されたキーに関連付けられた値を設定します.
/// </summary>
/// <param name="key">設定する値のキー.</param>
/// <param name="value">設定する値.</param>
public void SetValue(TKey key, TValue value)
{
    if (!this.TrySetValue(key, value)) throw new KeyNotFoundException();
}

/// <summary>
/// 指定された値に関連付けられたキーを設定します.
/// </summary>
/// <param name="value">設定するキーの値.</param>
/// <param name="key">設定するキー.</param>
public void SetKey(TValue value, TKey key)
{
    if (!this.TrySetKey(value, key)) throw new KeyNotFoundException();
}

/// <summary>
/// 指定されたキーに関連付けられた値の設定を試みます.
/// </summary>
/// <param name="key">設定する値のキー.</param>
/// <param name="value">設定する値.</param>
/// <returns>指定されたキーを持つ要素が LinkedDictionary<TKey,TValue> に含まれている場合は true,含まれていない場合は false.</returns>
public bool TrySetValue(TKey key, TValue value)
{
    if (this._keyToValues.ContainsKey(key))
    {
        var currentValue = this._keyToValues[key];
        this._keyToValues[key] = value;
        this._valueToKeys.Remove(currentValue);
        this._valueToKeys.Add(value, key);
        return true;
    }
    else return false;
}

/// <summary>
/// 指定された値に関連付けられたキーの設定を試みます.
/// </summary>
/// <param name="value">設定するキーの値.</param>
/// <param name="key">設定するキー.</param>
/// <returns>指定された値を持つ要素が LinkedDictionary<TKey,TValue> に含まれている場合は true,含まれていない場合は false.</returns>
public bool TrySetKey(TValue value, TKey key)
{
    if (this._valueToKeys.ContainsKey(value))
    {
        var currentKey = this._valueToKeys[value];
        this._valueToKeys[value] = key;
        this._keyToValues.Remove(currentKey);
        this._keyToValues.Add(key, value);
        return true;
    }
    else return false;
}

/// <summary>
/// 指定されたキーに関連付けられた値を設定または追加します.
/// </summary>
/// <param name="key">設定する値のキー.</param>
/// <param name="value">設定する値.</param>
public void SetValueOrAdd(TKey key, TValue value) 
{
    if (!this.TrySetValue(key, value)) this.Add(key, value);
}

/// <summary>
/// 指定された値に関連付けられたキーを設定または追加します.
/// </summary>
/// <param name="key">設定するキーの値.</param>
/// <param name="value">設定するキー.</param>
public void SetKeyOrAdd(TValue value, TKey key) 
{
    if (!this.TrySetKey(value, key)) this.Add(key, value);
}

/// <summary>
/// 指定されたキーに対して, TValue 型の既定値を設定または追加します.
/// </summary>
/// <param name="key">設定する値のキー.</param>
public void SetValueOrAddDefault(TKey key) => this.SetValueOrAdd(key, default(TValue));

/// <summary>
/// 指定された値に対して, TKey 型の既定値を設定または追加します.
/// </summary>
/// <param name="value">設定する値のキー.</param>
public void SeKeyOrAddDefault(TValue value) => this.SetValueOrAdd(default(TKey), value);

要素の追加

  • 要素の追加に関するいくつかのメソッドを定義する.
  • 通常のDictionary<TKey,TValue>では,追加するキーがnullである場合に例外を発生させるが,LinkedDictionary<TKey,TValue>では,キーと値のいずれかがnullである場合に例外を発生させる.
  • 通常のDictionary<TKey,TValue>では,追加するキーの重複によって例外を発生させるが,LinkedDictionary<TKey,TValue>では,キーと値それぞれの重複によって例外を発生させる.
  • コレクションの追加を行うAddRangeでは,コレクション内の何れかのキーまたは値が重複した場合に例外を発生させる一方,TryAddRangeではキーまたは値が重複しないもののみ追加を行う.
#region->method->add
/// <summary>
/// 指定されたキーと値を LinkedDictionary<TKey,TValue> に追加します.
/// </summary>
/// <param name="key">追加する要素のキー.</param>
/// <param name="value">追加する要素の値.</param>
public void Add(TKey key, TValue value)
{
    if (!this.TryAdd(key, value)) throw new ArgumentException();
}

/// <summary>
/// 指定された KeyValuePair<TKey, TValue> を LinkedDictionary<TKey,TValue> に追加します.
/// </summary>
/// <param name="item">追加する KeyValuePair<TKey, TValue>.</param>
public void Add(KeyValuePair<TKey, TValue> item) => this.Add(item.Key, item.Value);

/// <summary>
/// 指定された Tuple<TKey, TValue> を LinkedDictionary<TKey,TValue> に追加します.
/// </summary>
/// <param name="item">追加する Tuple<TKey, TValue>.</param>
public void Add((TKey Key, TValue Value) item) => this.Add(item.Key, item.Value);

/// <summary>
/// LinkedDictionary<TKey,TValue> に対して,指定されたキーと値の追加を試みます.
/// </summary>
/// <param name="key">追加する要素のキー.</param>
/// <param name="value">追加する要素の値.</param>
/// <returns>キー/値ペアが LinkedDictionary<TKey,TValue> に追加された場合は true,それ以外の場合は false.</returns>
public bool TryAdd(TKey key, TValue value)
{
    if (key == null) throw new ArgumentNullException(nameof(key));
    if (value == null) throw new ArgumentNullException(nameof(value));
    if (this._keyToValues.ContainsKey(key) || this._valueToKeys.ContainsKey(value)) return false;
    this._keyToValues.Add(key, value);
    this._valueToKeys.Add(value, key);
    return true;
}

/// <summary>
/// LinkedDictionary<TKey,TValue> に対して,指定された KeyValuePair<TKey, TValue> の追加を試みます.
/// </summary>
/// <param name="item">追加する KeyValuePair<TKey, TValue>.</param>
/// <returns></returns>
public bool TryAdd(KeyValuePair<TKey, TValue> item) => this.TryAdd(item.Key, item.Value);

/// <summary>
/// LinkedDictionary<TKey,TValue> に対して,指定された Tuple<TKey, TValue> の追加を試みます.
/// </summary>
/// <param name="item">追加する Tuple<TKey, TValue>.</param>
/// <returns></returns>
public bool TryAdd((TKey Key, TValue Value) item) => this.TryAdd(item.Key, item.Value);

/// <summary>
/// 指定した KeyValuePair<TKey, TValue> のコレクションを追加します.
/// </summary>
/// <param name="collection">追加する  KeyValuePair<TKey, TValue> のコレクション.</param>
public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> collection)
{
    foreach (var item in collection) this.Add(item);
}

/// <summary>
/// 指定した KeyValuePair<TKey, TValue> のコレクションのうち,キーと値が重複しないもののみを追加します.
/// </summary>
/// <param name="collection">追加する  KeyValuePair<TKey, TValue> のコレクション.</param>
public void TryAddRange(IEnumerable<KeyValuePair<TKey, TValue>> collection)
{
    foreach (var item in collection) this.TryAdd(item);
}

要素の削除

  • 要素の削除に関するいくつかのメソッドを定義する.
  • 要素の削除では,双方の内部辞書から削除を行うことに留意する.
#region->method->remove
/// <summary>
/// LinkedDictionary<TKey,TValue> からすべてのキーと値を削除します.
/// </summary>
public void Clear()
{
    this._keyToValues.Clear();
    this._valueToKeys.Clear();
}

/// <summary>
/// 指定したキーを持つ値を LinkedDictionary<TKey,TValue> から削除します.
/// </summary>
/// <param name="key">削除する要素のキー.</param>
/// <returns>要素が見つかり削除された場合は true.それ以外の場合は false.</returns>
public bool Remove(TKey key) => this.RemoveByKey(key);

/// <summary>
/// 指定したキーを持つ値を LinkedDictionary<TKey,TValue> から削除します.
/// </summary>
/// <param name="key">削除する要素のキー.</param>
/// <returns>要素が見つかり削除された場合は true.それ以外の場合は false.</returns>
public bool RemoveByKey(TKey key) => this.RemoveByKey(key, out var value);

/// <summary>
/// 指定した値を持つキーを LinkedDictionary<TKey,TValue> から削除します.
/// </summary>
/// <param name="value">削除する要素の値.</param>
/// <returns>要素が見つかり削除された場合は true.それ以外の場合は false.</returns>
public bool Remove(TValue value) => this.RemoveByValue(value);

/// <summary>
/// 指定した値を持つキーを LinkedDictionary<TKey,TValue> から削除します.
/// </summary>
/// <param name="value">削除する要素の値.</param>
/// <returns>要素が見つかり削除された場合は true.それ以外の場合は false.</returns>
public bool RemoveByValue(TValue value) => this.RemoveByValue(value, out var key);

/// <summary>
/// 指定されたキーを持つ値を LinkedDictionary<TKey,TValue> から削除し,その要素の値を value パラメーターにコピーします.
/// </summary>
/// <param name="key">削除する要素のキー.</param>
/// <param name="value">削除された要素の値.</param>
/// <returns>要素が見つかり削除された場合は true.それ以外の場合は false.</returns>
public bool Remove(TKey key, out TValue value) => this.RemoveByKey(key, out value);

/// <summary>
/// 指定されたキーを持つ値を LinkedDictionary<TKey,TValue> から削除し,その要素の値を value パラメーターにコピーします.
/// </summary>
/// <param name="key">削除する要素のキー.</param>
/// <param name="value">削除された要素の値.</param>
public bool RemoveByKey(TKey key, out TValue value)
{
    if (this._keyToValues.ContainsKey(key) && this._valueToKeys.ContainsKey(this._keyToValues[key]))
    {
        value = this._keyToValues[key];
        this._keyToValues.Remove(key);
        this._valueToKeys.Remove(value);
        return true;
    }
    else
    {
        value = default(TValue);
        return false;
    }
}

/// <summary>
/// 指定された値を持つキーを LinkedDictionary<TKey,TValue> から削除し,その要素のキーを key パラメーターにコピーします.
/// </summary>
/// <param name="value">削除する要素の値.</param>
/// <param name="key">削除する要素のキー.</param>
/// <returns>要素が見つかり削除された場合は true.それ以外の場合は false.</returns>
public bool Remove(TValue value, out TKey key) => this.RemoveByValue(value, out key);

/// <summary>
/// 指定された値を持つキーを LinkedDictionary<TKey,TValue> から削除し,その要素のキーを key パラメーターにコピーします.
/// </summary>
/// <param name="value">削除する要素の値.</param>
/// <param name="key">削除する要素のキー.</param>
/// <returns>要素が見つかり削除された場合は true.それ以外の場合は false.</returns>
public bool RemoveByValue(TValue value, out TKey key) 
{
    if (this._valueToKeys.ContainsKey(value) && this._keyToValues.ContainsKey(this._valueToKeys[value]))
    {
        key = this._valueToKeys[value];
        this._valueToKeys.Remove(value);
        this._keyToValues.Remove(key);
        return true;
    }
    else 
    {
        key = default(TKey);
        return false;
    }
}

/// <summary>
/// 指定された KeyValuePair<TKey, TValue> を LinkedDictionary<TKey,TValue> から削除します.
/// </summary>
/// <param name="item">削除する KeyValuePair<TKey, TValue>.</param>
/// <returns>要素が見つかり削除された場合は true.それ以外の場合は false.</returns>
public bool Remove(KeyValuePair<TKey, TValue> item) => this._keyToValues.ContainsKey(item.Key) && this._keyToValues[item.Key].Equals(item.Value) && this.Remove(item.Key);

判定

  • 要素の判定に関するいくつかのメソッドを定義する.
  • 実装は,内部辞書の処理に準じる.
#region->method->determinate
/// <summary>
/// 指定したキーを持つ要素が LinkedDictionary<TKey,TValue> に含まれるかどうかを判断します.
/// </summary>
/// <param name="key">検索するキー.</param>
/// <returns>指定されたキーを持つ要素が LinkedDictionary<TKey,TValue> に含まれている場合は true,含まれていない場合は false.</returns>
public bool ContainsKey(TKey key) => this._keyToValues.ContainsKey(key);

/// <summary>
/// 指定した値を持つ要素が LinkedDictionary<TKey,TValue> に含まれるかどうかを判断します.
/// </summary>
/// <param name="value">検索する値.</param>
/// <returns>指定された値を持つ要素が LinkedDictionary<TKey,TValue> に含まれている場合は true,含まれていない場合は false.</returns>
public bool ContainsValue(TValue value) => this._valueToKeys.ContainsKey(value);


/// <summary>
/// 指定した KeyValuePair<TKey, TValue> が LinkedDictionary<TKey,TValue> に含まれるかどうかを判断します.
/// </summary>
/// <param name="item">検索する KeyValuePair<TKey, TValue></param>
/// <returns></returns>
public bool Contains(KeyValuePair<TKey, TValue> item) => this.KeyValuePairs.Contains(item);

反復処理

  • 反復処理を行うためのIEnumeratorメソッドを定義する.
  • 実装は,KeyからValueへのマッピングを示す`内部辞書の処理に準じる.
#region->method->IEnumerator
/// <summary>
/// コレクションを反復処理する列挙子を返します.
/// </summary>
/// <returns>コレクションの繰り返し処理に使用できる列挙子.</returns>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => this._keyToValues.GetEnumerator();

/// <summary>
/// コレクションを反復処理する列挙子を返します.
/// </summary>
/// <returns>コレクションの反復処理に使用できる IEnumerator.</returns>
IEnumerator IEnumerable.GetEnumerator() => this._keyToValues.GetEnumerator();

コピー,変換

  • コピーと変換を行うためのいくつかのメソッドを定義する.
  • SelectValueSelectKeyは,双方向辞書内のValueまたはKeyに対して変換を行い新たな双方向辞書を生成する拡張メソッドからの応用.
#region->method->copy&convert
/// <summary>
/// 指定した配列インデックスを開始位置として,配列に ICollection<T> の要素をコピーします.
/// </summary>
/// <param name="array">ICollection<T> から要素がコピーされる 1 次元の配列. </param>
/// <param name="index">array 内のコピーの開始位置を示すインデックス.</param>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
{
    if (array == null) throw new ArgumentNullException(nameof(array));
    if (index < 0 || index > array.Length) throw new ArgumentOutOfRangeException(nameof(index));
    if (array.Length - index < this.Count) throw new ArgumentException();
    foreach (var item in this._keyToValues)
    {
        array[index++] = new KeyValuePair<TKey, TValue>(item.Key, item.Value);
    }
}

/// <summary>
/// シーケンスの各要素の値を Func<TValue, TValueResult> によって新しいフォームに射影し,既存のキーと新しい値による LinkedDictionary<TKey, TValueResult> を生成します.
/// </summary>
/// <typeparam name="TValueResult">変換後の要素の値の型.</typeparam>
/// <param name="valueSelector">要素の値の変換を表すデリゲート.</param>
/// <returns>生成された LinkedDictionary<TKey, TValueResult>.</returns>
public LinkedDictionary<TKey, TValueResult> SelectValue<TValueResult>(Func<TValue, TValueResult> valueSelector)
    => new LinkedDictionary<TKey, TValueResult>(this.KeyValuePairs.Select(item => new KeyValuePair<TKey, TValueResult>(item.Key, valueSelector(item.Value))));

/// <summary>
/// シーケンスの各要素の値を Func<TKey, TKeyResult> によって新しいフォームに射影し,新しいキーと既存の値による LinkedDictionary<TKeyResult, TValue> を生成します.
/// </summary>
/// <typeparam name="TKeyResult">変換後の要素のキーの型.</typeparam>
/// <param name="keySelector">要素のキーの変換を表すデリゲート.</param>
/// <returns>生成された LinkedDictionary<TKey, TValueResult>.</returns>
public LinkedDictionary<TKeyResult, TValue> SelectKey<TKeyResult>(Func<TKey, TKeyResult> keySelector)
    => new LinkedDictionary<TKeyResult, TValue>(this.KeyValuePairs.Select(item => new KeyValuePair<TKeyResult, TValue>(keySelector(item.Key), item.Value)));

ソート

  • TKeyTValueによる既定のソートを行うメソッドを定義する.
  • 加えて,独自の変換デリゲートでもソートができるようにした.
#region->method->sort
///// <summary>
///// 要素のキーの既定の比較子を使用して,LinkedDictionary<TKey,TValue> 全体内の要素をそのキーによって並べ替えます.
///// </summary>
public void SortByKey() => this.Sort(item => item.Key);

///// <summary>
///// 要素の値の既定の比較子を使用して,LinkedDictionary<TKey,TValue> 全体内の要素をその値によって並べ替えます.
///// </summary>
public void SortByValue() => this.Sort(item => item.Value);

///// <summary>
///// 指定した変換デリゲートを使用して,LinkedDictionary<TKey,TValue> 全体内の要素をその値によって並べ替えます.
///// </summary>
public void Sort<T>(Func<KeyValuePair<TKey, TValue>, T> Selecter)
{
    var collection = this.KeyValuePairs.OrderBy(Selecter).ToArray();
    this.Clear();
    this.AddRange(collection);
}

##課題

  • TKeyTValueが同型であるとき,Item[TKey]Item[TValue]プロパティや,Remove(TKey)Remove(TValue)メソッド等で名前の競合が発生する.
  • 今回はそれぞれ別名のプロパティやメソッドを用意して回避可能としたが,他に良い方法は無いだろうか.例えば,where TKey != TValueのようなジェネリック型制約ができるとか…
4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?