Dictionary で複数の値をキーにしたい
Dictionary で複数の値をキーにする実装方法を紹介します。
概要
Dictionary ではキーが1つありますが、実装内容によってはキーの値を2つ以上にしたい場合があります。
この記事では、Dictionaryでキーの値を2つ以上利用するコードを紹介します。
事前準備
テスト用のプログラムを作成します。
UI
下図のUIを作成します。
コード
下記コードを記述します。準備段階ではbutton1のみを実装します。
namespace MultiKeyDictionary
{
public partial class FormMain : Form
{
public FormMain()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Dictionary<int, MyItem> dic = new Dictionary<int, MyItem>();
MyItem m;
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic.Add(m.id, m);
m = new MyItem() { id = 2, product_code = 102, product_sub_code = 2, category = "オフィス", name = "シンプルデスク", price = 26400, stock = 3 };
dic.Add(m.id, m);
m = new MyItem() { id = 3, product_code = 103, product_sub_code = 4, category = "ホーム", name = "やわらかベッド", price = 43000, stock = 2 };
dic.Add(m.id, m);
m = new MyItem() { id = 4, product_code = 104, product_sub_code = 8, category = "ホーム", name = "おしゃれスタンド", price = 124000, stock = 1 };
dic.Add(m.id, m);
m = new MyItem() { id = 5, product_code = 301, product_sub_code = 1, category = "オフィス", name = "エルゴチェア", price = 82000, stock = 4 };
dic.Add(m.id, m);
m = new MyItem() { id = 6, product_code = 401, product_sub_code = 2, category = "ホーム", name = "ウッドデスク", price = 22000, stock = 5 };
dic.Add(m.id, m);
MyItem sm = dic[3];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}",sm.name, sm.price, sm.stock);
}
}
}
Dictionaryに挿入するオブジェクトのクラスです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MultiKeyDictionary
{
public class MyItem
{
public int id;
public int product_code;
public int product_sub_code;
public string category;
public string name;
public int stock;
public int price;
}
}
実行結果
プロジェクトを実行し[button1]をクリックします。
Dictionaryから、id=3のMyItemの要素を探し、オブジェクトの情報をテキストボックスに表示します。
方法1: キーのクラスを定義する
Dictionaryのキー用にクラスを定義する方法です。
このセクションで紹介する方法は、キー用のクラスを定義してクラスを実装する方法です。
Dictionaryでクラスの一致を判定するために、
GetHashCode()
メソッドと
Equals
メソッドをオーバーライドします。
コード
キー用のクラスの以下のコードを作成します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MultiKeyDictionary
{
public class MyKey
{
public int stock;
public string category;
public override int GetHashCode()
{
return this.stock.GetHashCode() + this.category.GetHashCode();
}
public override bool Equals(object obj)
{
MyKey comp = obj as MyKey;
if (comp == null) return false;
return this.stock == comp.stock && this.category == comp.category;
}
}
}
フォームのbutton2に以下のコードを実装します。
private void button2_Click(object sender, EventArgs e)
{
Dictionary<MyKey, MyItem> dic = new Dictionary<MyKey, MyItem>();
MyItem m;
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic.Add(new MyKey() { stock=m.stock, category=m.category}, m);
m = new MyItem() { id = 2, product_code = 102, product_sub_code = 2, category = "オフィス", name = "シンプルデスク", price = 26400, stock = 3 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
m = new MyItem() { id = 3, product_code = 103, product_sub_code = 4, category = "ホーム", name = "やわらかベッド", price = 43000, stock = 2 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
m = new MyItem() { id = 4, product_code = 104, product_sub_code = 8, category = "ホーム", name = "おしゃれスタンド", price = 124000, stock = 1 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
m = new MyItem() { id = 5, product_code = 301, product_sub_code = 1, category = "オフィス", name = "エルゴチェア", price = 82000, stock = 4 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
m = new MyItem() { id = 6, product_code = 401, product_sub_code = 2, category = "ホーム", name = "ウッドデスク", price = 22000, stock = 5 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
MyKey mk = new MyKey() { stock=1, category="ホーム"};
MyItem sm = dic[mk];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
}
}
解説
GetHashCode はオブジェクトの比較に用いるハッシュ値を算出するメソッドです。
今回はメンバ変数の一致がオブジェクトの一致としたいので、GetHashCodeメソッドでは比較に用いるメンバ変数のハッシュの和を返す処理にします。
Equalsは完全な一致かどうかを判定するメソッドのため、比較に用いるメンバ変数が完全に一致している場合に
true
を返す処理としています。
Dictionaryオブジェクトはキーに MyKeyクラスを指定して作成します。
Dictionary<MyKey, MyItem> dic = new Dictionary<MyKey, MyItem>();
Dictionaryへの挿入時に MyKeyクラスを作成して、挿入するMyItemオブジェクトの stockとcategory の値を設定しています。
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic.Add(new MyKey() { stock=m.stock, category=m.category}, m);
検索する場合はMyKeyオブジェクトを作成し、検索値をMyKeyオブジェクトのメンバ変数に代入して、DictionaryオブジェクトにMyKeyオブジェクトを与えて検索します。
MyKey mk = new MyKey() { stock=1, category="ホーム"};
MyItem sm = dic[mk];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
実行結果
プロジェクトを実行し[button2]をクリックします。stockの値が1、categoryの値が"ホーム"である要素を探し、情報をテキストボックスに表示します。
テキストボックスに値が表示され、一致する要素が検索ができていることが確認できます。
注意
stock と categoryをキーにしているため、両方が他の項目と一致する要素を追加するとキーの衝突が起きるため注意が必要です。
完全に一致するデータを挿入しない工夫または仕様が必要です。
private void button2_Click(object sender, EventArgs e)
{
Dictionary<MyKey, MyItem> dic = new Dictionary<MyKey, MyItem>();
MyItem m;
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic.Add(new MyKey() { stock=m.stock, category=m.category}, m);
m = new MyItem() { id = 2, product_code = 102, product_sub_code = 2, category = "オフィス", name = "シンプルデスク", price = 26400, stock = 3 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
m = new MyItem() { id = 3, product_code = 103, product_sub_code = 4, category = "ホーム", name = "やわらかベッド", price = 43000, stock = 2 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
m = new MyItem() { id = 4, product_code = 104, product_sub_code = 8, category = "ホーム", name = "おしゃれスタンド", price = 124000, stock = 1 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
m = new MyItem() { id = 5, product_code = 301, product_sub_code = 1, category = "オフィス", name = "エルゴチェア", price = 82000, stock = 4 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
m = new MyItem() { id = 6, product_code = 401, product_sub_code = 2, category = "ホーム", name = "ウッドデスク", price = 22000, stock = 5 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
//stockとcategoryが両方、他の項目と一致してしまう要素を追加
m = new MyItem() { id = 7, product_code = 801, product_sub_code = 4, category = "オフィス", name = "仮眠ベッド", price = 22000, stock = 3 };
dic.Add(new MyKey() { stock = m.stock, category = m.category }, m);
MyKey mk = new MyKey() { stock=1, category="ホーム"};
MyItem sm = dic[mk];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
}
実行すると、System.ArgumentException 例外が発生します。
エラーメッセージ
System.ArgumentException
HResult=0x80070057
Message=An item with the same key has already been added. Key: MultiKeyDictionary.MyKey
Source=System.Private.CoreLib
方法2: Tuple(タプル)を利用する
タプルを利用してDictionaryオブジェクトで複数のキー値を扱う方法です。
この方式ではキー用のクラスを定義しなくてよいため記述するコードの量が減りシンプルなコードになります。
タプルの動作の詳細は
こちらの記事を参照してください。
コード
button3のClickイベントハンドラに以下のコードを記述します。
private void button3_Click(object sender, EventArgs e)
{
Dictionary<Tuple<int,string>, MyItem> dic = new Dictionary<Tuple<int, string>, MyItem>();
MyItem m;
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 2, product_code = 102, product_sub_code = 2, category = "オフィス", name = "シンプルデスク", price = 26400, stock = 3 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 3, product_code = 103, product_sub_code = 4, category = "ホーム", name = "やわらかベッド", price = 43000, stock = 2 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 4, product_code = 104, product_sub_code = 8, category = "ホーム", name = "おしゃれスタンド", price = 124000, stock = 1 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 5, product_code = 301, product_sub_code = 1, category = "オフィス", name = "エルゴチェア", price = 82000, stock = 4 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 6, product_code = 401, product_sub_code = 2, category = "ホーム", name = "ウッドデスク", price = 22000, stock = 5 };
dic.Add(new Tuple<int, string>(m.stock, m.category),m);
MyItem sm = dic[new Tuple<int, string>(4, "オフィス")];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
}
解説
Dictionaryオブジェクトの作成時に、キーの型にタプル型を指定します。今回は、int と string 型の2つの値を持つタプル型を指定しています。
Dictionary<Tuple<int,string>, MyItem> dic = new Dictionary<Tuple<int, string>, MyItem>();
Dictionaryへ挿入するMyItemクラスはこれまでの記述方法と変更はありません。
Dictionaryへ挿入する際に、Tuple<T>クラスを作成し、キーとなる値を代入したのち、Dictionaryへ挿入します。
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
値の検索はDictionaryオブジェクトの添え字にTupleのオブジェクトを与えます。
MyItem sm = dic[new Tuple<int, string>(4, "オフィス")];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
実行結果
プロジェクトを実行し[button3]をクリックします。stockの値が4、categoryの値が"オフィス"である要素を探し、情報をテキストボックスに表示します。
テキストボックスに値が表示され、一致する要素が検索ができていることが確認できます。
注意
先の例と同様に、同じキーの値になる要素を追加すると例外が発生するため注意が必要です。
private void button3_Click(object sender, EventArgs e)
{
Dictionary<Tuple<int,string>, MyItem> dic = new Dictionary<Tuple<int, string>, MyItem>();
MyItem m;
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 2, product_code = 102, product_sub_code = 2, category = "オフィス", name = "シンプルデスク", price = 26400, stock = 3 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 3, product_code = 103, product_sub_code = 4, category = "ホーム", name = "やわらかベッド", price = 43000, stock = 2 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 4, product_code = 104, product_sub_code = 8, category = "ホーム", name = "おしゃれスタンド", price = 124000, stock = 1 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 5, product_code = 301, product_sub_code = 1, category = "オフィス", name = "エルゴチェア", price = 82000, stock = 4 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
m = new MyItem() { id = 6, product_code = 401, product_sub_code = 2, category = "ホーム", name = "ウッドデスク", price = 22000, stock = 5 };
dic.Add(new Tuple<int, string>(m.stock, m.category),m);
//
m = new MyItem() { id = 7, product_code = 108, product_sub_code = 2, category = "オフィス", name = "ひろびろパーティション", price = 16000, stock = 4 };
dic.Add(new Tuple<int, string>(m.stock, m.category), m);
MyItem sm = dic[new Tuple<int, string>(4, "オフィス")];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
}
エラーメッセージ
System.ArgumentException
HResult=0x80070057
Message=An item with the same key has already been added. Key: (4, オフィス)
Source=System.Private.CoreLib
方法3: 入れ子にする
Dictionaryオブジェクトを入れ子にする方法です。
やや強引な実装にも見えますが、アイテムの個数が多い場合は、絞り込みによる検索スピードも速く有用な場合もあります。
コード
button4のClickイベントハンドラに以下のコードを記述します。
private void button4_Click(object sender, EventArgs e)
{
Dictionary<string, MyItem> dic1;
Dictionary<int, Dictionary<string, MyItem>> dic2 = new Dictionary<int, Dictionary<string, MyItem>>();
MyItem m;
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 2, product_code = 102, product_sub_code = 2, category = "オフィス", name = "シンプルデスク", price = 26400, stock = 3 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 3, product_code = 103, product_sub_code = 4, category = "ホーム", name = "やわらかベッド", price = 43000, stock = 2 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 4, product_code = 104, product_sub_code = 8, category = "ホーム", name = "おしゃれスタンド", price = 124000, stock = 1 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 5, product_code = 301, product_sub_code = 1, category = "オフィス", name = "エルゴチェア", price = 82000, stock = 4 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 6, product_code = 401, product_sub_code = 2, category = "ホーム", name = "ウッドデスク", price = 22000, stock = 5 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
MyItem sm = dic2[401]["ウッドデスク"];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
}
解説
2つのDictionaryオブジェクトを作成します。一つは入れ子になる内側のDictionaryオブジェクトになり、もう一つがDctionaryオブジェクトを値とする、Dictionaryオブジェクトになります。
Dictionary<string, MyItem> dic1;
Dictionary<int, Dictionary<string, MyItem>> dic2 = new Dictionary<int, Dictionary<string, MyItem>>();
要素のMyItemオブジェクトを作成し、値を設定の後、内側のdic1 Dictionaryオブジェクトに挿入します。キーの値はnameメンバの値とします。
挿入したdic1 Dictionaryオブジェクトを dic2 Dictionaryに挿入します。dic2のキーの値は、product_code メンバの値とします。
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
検索はDictionaryオブジェクトに対し2次元の添え字を記述する方法でアクセスします。
MyItem sm = dic2[401]["ウッドデスク"];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
実行結果
プロジェクトを実行し[button4]をクリックします。product_code の値が401、nameの値が"ウッドデスク"である要素を探し、情報をテキストボックスに表示します。
テキストボックスに値が表示され、一致する要素が検索ができていることが確認できます。
注意
この方法では、内側のDictionaryオブジェクトは1つの要素しか挿入されないため、キーの重複は起きませんが、
外側のDictionaryオブジェクトに対してキーの重複が出ないようにする必要があるため、注意が必要です。
private void button4_Click(object sender, EventArgs e)
{
Dictionary<string, MyItem> dic1;
Dictionary<int, Dictionary<string, MyItem>> dic2 = new Dictionary<int, Dictionary<string, MyItem>>();
MyItem m;
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 2, product_code = 102, product_sub_code = 2, category = "オフィス", name = "シンプルデスク", price = 26400, stock = 3 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 3, product_code = 103, product_sub_code = 4, category = "ホーム", name = "やわらかベッド", price = 43000, stock = 2 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 4, product_code = 104, product_sub_code = 8, category = "ホーム", name = "おしゃれスタンド", price = 124000, stock = 1 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 5, product_code = 301, product_sub_code = 1, category = "オフィス", name = "エルゴチェア", price = 82000, stock = 4 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
m = new MyItem() { id = 6, product_code = 401, product_sub_code = 2, category = "ホーム", name = "ウッドデスク", price = 22000, stock = 5 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
//
m = new MyItem() { id = 7, product_code = 301, product_sub_code = 2, category = "ホーム", name = "リラックスチェア", price = 79800, stock = 3 };
dic1 = new Dictionary<string, MyItem>();
dic1.Add(m.name, m);
dic2.Add(m.product_code, dic1);
MyItem sm = dic2[401]["ウッドデスク"];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
}
Product_codeの値しか重複していませんが、例外が発生します。
エラーメッセージ
System.ArgumentException
HResult=0x80070057
Message=An item with the same key has already been added. Key: 301
Source=System.Private.CoreLib
改良版
上記のコードでは内側のdictionaryに1つしかオブジェクトが挿入されないことや、外側のキーに重複があるだけで例外が発生するため、次のコードに改良できます。
挿入する際に、1つ目のキーで外側のDictionaryを検索し、キーが存在する場合には、値のDictionaryオブジェクトにMyItemオブジェクトを追加する動作にしています。
この方法にすると、両方のキーが一致した場合には、例外が発生しますが、外側のDictionaryのキーの一致だけでは例外が発生しなくなります。
private void button5_Click(object sender, EventArgs e)
{
Dictionary<int, Dictionary<string, MyItem>> dic = new Dictionary<int, Dictionary<string, MyItem>>();
MyItem m;
m = new MyItem() { id = 1, product_code = 101, product_sub_code = 1, category = "オフィス", name = "シンプルチェア", price = 32800, stock = 8 };
InsertObj(dic, m);
m = new MyItem() { id = 2, product_code = 102, product_sub_code = 2, category = "オフィス", name = "シンプルデスク", price = 26400, stock = 3 };
InsertObj(dic, m);
m = new MyItem() { id = 3, product_code = 103, product_sub_code = 4, category = "ホーム", name = "やわらかベッド", price = 43000, stock = 2 };
InsertObj(dic, m);
m = new MyItem() { id = 4, product_code = 104, product_sub_code = 8, category = "ホーム", name = "おしゃれスタンド", price = 124000, stock = 1 };
InsertObj(dic, m);
m = new MyItem() { id = 5, product_code = 301, product_sub_code = 1, category = "オフィス", name = "エルゴチェア", price = 82000, stock = 4 };
InsertObj(dic, m);
m = new MyItem() { id = 6, product_code = 401, product_sub_code = 2, category = "ホーム", name = "ウッドデスク", price = 22000, stock = 5 };
InsertObj(dic, m);
m = new MyItem() { id = 7, product_code = 301, product_sub_code = 2, category = "ホーム", name = "リラックスチェア", price = 79800, stock = 3 };
InsertObj(dic, m);
MyItem sm = dic[102]["シンプルデスク"];
textBox1.Text += string.Format("Name:{0} - Price:{1:d} - Stock:{2:d}", sm.name, sm.price, sm.stock);
}
private void InsertObj(Dictionary<int, Dictionary<string, MyItem>> dic, MyItem m)
{
if (dic.ContainsKey(m.product_code) == true) {
dic[m.product_code].Add(m.name, m);
}
else {
Dictionary<string, MyItem> idic = new Dictionary<string, MyItem>();
idic.Add(m.name, m);
dic.Add(m.product_code, idic);
}
}
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用