ヽ|∵|ゝ(Fantom) の 開発blog? ホーム »連想配列
このページの記事一覧

【C#】連想配列(Dictionary)を値 or キーでソート  


 このテーマはググればいくらでも出てくるので、どちらかというと自分用メモ。先に述べてしまうと、初めの LINQ 的な書き方が一番簡単だろう。ただ、他の方法もあったので、それも少しまとめてみた感じ。色々な書き方を知っておくと、他言語に移植するとき等にも役に立つからだ。




■IEnumerable でのソート (LINQ)

 LINQ (System.Linq) が使えるなら、とりあえずこれを使うのが一番楽だろう。内容的には Enumerable(IEnumerable) を使うことになる。キーでのソートも、値でのソートもプロパティを変えるだけで同様にできる。

●値でのソート (IEnumerable)
using System;
using System.Collections.Generic;
using System.Linq;

Dictionary<string, int> dic = new Dictionary<string, int>() {
{ "Becky", 85 },
{ "Daisy", 72 },
{ "Alice", 95 },
{ "Eliza", 78 },
{ "Cindy", 100 },
};

var sorted = dic.OrderBy((x) => x.Value); //昇順
//var sorted = dic.OrderByDescending((x) => x.Value); //降順

foreach (var v in sorted)
{
Console.WriteLine(v.Key + " => " + v.Value);
}

Daisy => 72
Eliza => 78
Becky => 85
Alice => 95
Cindy => 100

 コメントアウトの方は降順になる。キーでのソートはソート対象を変えるだけで良い(x.Value → x.Key に変更)。

●キーでのソート (IEnumerable)
var sorted = dic.OrderBy((x) => x.Key);  //昇順
//var sorted = dic.OrderByDescending((x) => x.Key); //降順

Alice => 95
Becky => 85
Cindy => 100
Daisy => 72
Eliza => 78




■KeyValuePair の List でのソート (ラムダ式)

 これはキーと値のペア(KeyValuePair)のリストを作って、それをソートする方法だ。他の言語でもよく使われる方法なので、覚えておくと役に立つ。List.Sort() はラムダ式が使えるようなので、簡潔に書くことも可能だ。

●値でのソート (KeyValuePair の List と ラムダ式)
using System;
using System.Collections.Generic;

Dictionary<string, int> dic = new Dictionary<string, int>() {
{ "Becky", 85 },
{ "Daisy", 72 },
{ "Alice", 95 },
{ "Eliza", 78 },
{ "Cindy", 100 },
};

List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(dic);
list.Sort((a, b) => a.Value - b.Value); //昇順
//list.Sort((a, b) => b.Value - a.Value); //降順

foreach (var v in list)
{
Console.WriteLine(v.Key + " => " + v.Value);
}

Daisy => 72
Eliza => 78
Becky => 85
Alice => 95
Cindy => 100

 コメントアウトの方は降順になる。キーでのソートはソート対象を変えるだけで良い(x.Value → x.Key に変更)。なお、比較には CompareTo() を使っている。

●キーでのソート (KeyValuePair の List と ラムダ式)
list.Sort((a, b) => a.Key.CompareTo(b.Key));  //昇順
//list.Sort((a, b) => b.Key.CompareTo(a.Key)); //降順

Alice => 95
Becky => 85
Cindy => 100
Daisy => 72
Eliza => 78




■KeyValuePair の List でのソート (delegate)

 これは上記のラムダ式版の別表記と言っても良いのだが、改めて delegate を使えると覚えておくと、例えば、リアルタイムにソート方法を変更するような実装もできると考えることができる。まぁ、引き出しは多いことに越したことはない。

●値でのソート (KeyValuePair の List と delegate)
using System;
using System.Collections.Generic;

Dictionary<string, int> dic = new Dictionary<string, int>() {
{ "Becky", 85 },
{ "Daisy", 72 },
{ "Alice", 95 },
{ "Eliza", 78 },
{ "Cindy", 100 },
};

List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(dic);
list.Sort(delegate(KeyValuePair<string, int> a, KeyValuePair<string, int> b) {
return a.Value - b.Value; //昇順
//return b.Value - a.Value; //降順
});

foreach (var v in list)
{
Console.WriteLine(v.Key + " => " + v.Value);
}

Daisy => 72
Eliza => 78
Becky => 85
Alice => 95
Cindy => 100

 コメントアウトの方は降順になる。キーでのソートはソート対象を変えるだけで良い(x.Value → x.Key に変更)。なお、比較には CompareTo() を使っている。

●キーでのソート (KeyValuePair の List と delegate)
list.Sort(delegate(KeyValuePair<string, int> a, KeyValuePair<string, int> b) {
return a.Key.CompareTo(b.Key); //昇順
//return b.Key.CompareTo(a.Key); //降順
});

Alice => 95
Becky => 85
Cindy => 100
Daisy => 72
Eliza => 78




■KeyValuePair の List でのソート (IComparer)

 ICompare(Comparer) も使えるのでやってみよう。このような比較関数(クラス)は他の言語でもよくあるので、移植もしやすい。ちなみに以前書いた Java での連想配列(Map)でのソートと全く同じ構成となる。

●値(or キー)でのソート (KeyValuePair の List と IComparer)
using System;
using System.Collections.Generic;

public static void Main()
{
Dictionary<string, int> dic = new Dictionary<string, int>() {
{ "Becky", 85 },
{ "Daisy", 72 },
{ "Alice", 95 },
{ "Eliza", 78 },
{ "Cindy", 100 },
};

List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(dic);
list.Sort(new MyComparer<string, int>());

foreach (var v in list)
{
Console.WriteLine(v.Key + " => " + v.Value);
}
}

//比較クラス(関数)
public class MyComparer<K,V> : IComparer<KeyValuePair<K,V>>
where K : IComparable
where V : IComparable
{
public int Compare(KeyValuePair<K,V> a, KeyValuePair<K,V> b) {
return a.Value.CompareTo(b.Value); //値 昇順
//return b.Value.CompareTo(a.Value); //値 降順
//return a.Key.CompareTo(b.Key); //キー 昇順
//return b.Key.CompareTo(a.Key); //キー 降順
}
}

Daisy => 72
Eliza => 78
Becky => 85
Alice => 95
Cindy => 100

 上記の例では1つのクラスでまとめてしまったため、型パラメーターの制約を複数書いてしまっているが、値のみ(V)・キーのみ(K)とわかっているなら、制約はどちらかだけでも良い。同じようなことは delegate 版でもできると思うが、1つのクラスにまとめた利点としては、プロパティなどを追加して、いつでもソート方法を変更できる・同じソート方法を共有できること等にある。用途に合わせて使い分けるのも良いだろう。




■キーと値の2つの配列でのソート

 もう1つ、今回の連想配列のテーマからは少し外れるのだが、C# には「ソート対象となる1次元配列と、その項目に対応する別の1次元配列」をソートできる Array.Sort(Array, Array) がオーバーロードされている。まるで連想配列をソートするような感じになる。

●キーと値の2つの配列でのソート
using System;
using System.Collections.Generic;

public static void Main()
{
string[] name = {"Alice", "Becky", "Cindy", "Daisy", "Eliza"};
int[] value = {95, 85, 100, 72, 78};

Array.Sort(value, name); //昇順
//Array.Sort(value, name, new RevComparer<int>()); //降順

for (int i = 0; i < value.Length; i++)
{
Console.WriteLine(name[i] + " => " + value[i]);
}
}

//逆順(降順)
public class RevComparer<T> : IComparer<T> where T : IComparable
{
public int Compare(T a, T b) {
return b.CompareTo(a); //降順
}
}

Daisy => 72
Eliza => 78
Becky => 85
Alice => 95
Cindy => 100

 引数は Array.Sort(ソート対象の配列, 対応する配列, [IComparer]) となっている。

 ちなみに、コメントアウトされている「RevComparer」(逆順用 Comparer) で降順ソートすると結果は以下のようになる。

Cindy => 100
Alice => 95
Becky => 85
Eliza => 78
Daisy => 72

 ソートのキーとなる配列と値の配列(項目に対応する配列)は別々に作るので、型に自由が利くのが特徴だ。値の配列は何でも良いので、クラスや構造体の配列などをソートすることもできる。普段はクラスや構造体でデータを保持しておいて、ソートしたいときだけキーとなる配列を生成する等もできるだろう。

 Comparer(RevComparer)などもあらかじめ static で作っておけば(int など利用頻度が高い型を別に作っておけば)、毎回 new しなくて済むので実行速度がはやくなる。色々工夫してみると良いだろう。


(関連記事)
【C#】2次元配列(ジャグ配列・多次元配列)のソート
【C#】多次元配列とジャグ配列(2次元配列)のサイズ(長さ)、相互変換など


スポンサーサイト

category: C#

thread: プログラミング

janre: コンピュータ

tag: C#  連想配列  配列操作  ソート 
tb: 0   cm: --

【Java】配列, リスト(List), 連想配列(Map) の初期化  


 今までのサンプルコードにも散々使ってるけど、少し複雑なものになると書くのが結構大変なので、テンプレ用にまとめてみようと思った。調べてみたら意外と使われていない(?)んだよね。ただし、あくまで値をあらかじめセットしておくグローバルな初期化のみでやってみる。ローカルの場合はブロック表記「{~}」が不要なだけで、基本的には同じ。

 よく使う例として、リスト(List)リストを配列化したもの(List[])連想配列(Map)もついでにやってみよう。

●1次元配列の初期化 : int[]
public class ArrayInitTest {

int[] arr = {1, 2, 3};

public static void main(String[] args) throws Exception {
new ArrayInitTest();
}

public ArrayInitTest() {
for (int v : arr) {
System.out.print(v + " ");
}
}
}

1 2 3

 2次元での初期化も同じように書ける。

●2次元配列の初期化 : int[][]
public class ArrayInitTest {

int[][] arr = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
};

public static void main(String[] args) throws Exception {
new ArrayInitTest();
}

public ArrayInitTest() {
for (int[] is : arr) {
for (int v : is) {
System.out.print(v + " ");
}
System.out.println();
}
}
}

1 2 3
4 5 6
7 8 9

 例をグローバル変数にしたのは、初期化ブロック(「{~}」で囲まれた部分)というものが使えるというのを説明するためだ。もちろんローカル変数なら必要ない。連番とか計算値をあらかじめ入れておくのには良い。内容的には全く同じものになる。

●初期化ブロックを使った、2次元配列の初期化 : int[][]
public class ArrayInitTest {

int[][] arr = new int[3][3];
{
for (int i = 0, p = 1; i < 3; i++) {
for (int j = 0; j < 3; j++) {
arr[i][j] = p++;
}
}
}

public static void main(String[] args) throws Exception {
new ArrayInitTest();
}

public ArrayInitTest() {
for (int[] is : arr) {
for (int v : is) {
System.out.print(v + " ");
}
System.out.println();
}
}
}

1 2 3
4 5 6
7 8 9

 静的(static)な変数やメソッドとして使いたい場合は、ブロック自体に「static」を書くと良い(クラス共通となる)。

●静的初期化ブロックを使った、2次元配列の初期化 : static int[][]
public class ArrayInitTest {

static int[][] arr = new int[3][3];
static {
for (int i = 0, p = 1; i < 3; i++) {
for (int j = 0; j < 3; j++) {
arr[i][j] = p++;
}
}
}

public static void main(String[] args) throws Exception {
for (int[] is : arr) {
for (int v : is) {
System.out.print(v + " ");
}
System.out.println();
}
}
}

1 2 3
4 5 6
7 8 9


- - - - - - - - - - - - - - - - - - 
■リスト(List) の初期化

 次にリスト(List)を初期化してみよう。「{~}」が2重になるが、メソッドを使った初期化もできる。

●リストの初期化 : List<E>
public class ListInitTest {

@SuppressWarnings("serial")
List<Integer> list = new ArrayList<Integer>(){
{
add(1); add(2); add(3);
}
};

public static void main(String[] args) throws Exception {
new ListInitTest();
}

public ListInitTest() {
for (int v : list) {
System.out.print(v + " ");
}
}
}

1 2 3

 通常の配列と同じように、初期化ブロックを使って、for ループなどで追加しても良いだろう。「@SuppressWarnings()」は警告を消しているだけなので、無くても動く。

 リストの2次元(ネスト)も同じように考えればできる。ちょっと中括弧「{~}」が多くなるが、ある程度まとめてしまえば、それほど難しくはないだろう。

●2次元のリスト(ネスト)の初期化 : List<List<E>>
public class ListInitTest {

@SuppressWarnings("serial")
List<List<Integer>> list = new ArrayList<List<Integer>>(){{
add(new ArrayList<Integer>(){{
add(1); add(2); add(3);
}});
add(new ArrayList<Integer>(){{
add(4); add(5); add(6);
}});
add(new ArrayList<Integer>(){{
add(7); add(8); add(9);
}});
}};

public static void main(String[] args) throws Exception {
new ListInitTest();
}

public ListInitTest() {
for (List<Integer> li : list) {
for (int is : li) {
System.out.print(is + " ");
}
System.out.println();
}
}
}

1 2 3
4 5 6
7 8 9

 通常の配列のときのように、連番や計算値などなら初期化ブロックを使うのも良いだろう。

●初期化ブロックを使った、2次元のリスト(ネスト)の初期化 : List<List<E>>
public class ListInitTest {

List<List<Integer>> list = new ArrayList<List<Integer>>();
{
for (int i = 0, p = 1; i < 3; i++) {
list.add(new ArrayList<Integer>());
for (int j = 0; j < 3; j++) {
list.get(i).add(p++);
}
}
}

public static void main(String[] args) throws Exception {
new ListInitTest();
}

public ListInitTest() {
for (List<Integer> li : list) {
for (int is : li) {
System.out.print(is + " ");
}
System.out.println();
}
}
}

1 2 3
4 5 6
7 8 9


 リストと配列を絡めることもできる。長さが決まってるなら、通常の配列を使った方が実行速度が速いので、選択肢の1つとして考慮してみるのも良いだろう。構造的には配列の初期化リストの初期化を混ぜあわせたものになる。隣接リストなどにも向いている気がする。

●配列化されたリストを初期化する : List<E>[]
public class ListInitTest {

@SuppressWarnings({ "rawtypes", "serial" })
List[] list = {
new ArrayList<Integer>(){{
add(1); add(2); add(3);
}},
new ArrayList<Integer>(){{
add(4); add(5); add(6);
}},
new ArrayList<Integer>(){{
add(7); add(8); add(9);
}},
};

public static void main(String[] args) throws Exception {
new ListInitTest();
}

public ListInitTest() {
for (List<Integer> li : list) {
for (int is : li) {
System.out.print(is + " ");
}
System.out.println();
}
}
}

1 2 3
4 5 6
7 8 9

 もちろん同じように、初期化ブロックを使っても良い。

●初期化ブロックを使って、配列化されたリストを初期化する : List<E>[]
public class ListInitTest {

@SuppressWarnings("unchecked")
List<Integer>[] list = new List[3];
{
for (int i = 0, p = 1; i < list.length; i++) {
list[i] = new ArrayList<Integer>();
for (int j = 0; j < 3; j++) {
list[i].add(p++);
}
}
}

public static void main(String[] args) throws Exception {
new ListInitTest();
}

public ListInitTest() {
for (List<Integer> li : list) {
for (int is : li) {
System.out.print(is + " ");
}
System.out.println();
}
}
}

1 2 3
4 5 6
7 8 9


- - - - - - - - - - - - - - - - - - 
■連想配列(Map) の初期化

 ここまで見てれば、もう連想配列(Map)の初期化も想像がつくだろう。クラスで共通に使うなら静的な初期化ブロック「static」にするのも良い。

●連想配列(Map) の初期化 : Map<K, V>
public class MapInitTest {

@SuppressWarnings("serial")
Map<String, Integer> map = new TreeMap<String, Integer>() {
{
put("Candy", 6);
put("Eliza", 9);
put("Becky", 10);
put("Alice", 13);
put("Daisy", 99);
}
};

public static void main(String[] args) throws Exception {
new MapInitTest();
}

public MapInitTest() {
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
}
}

Alice = 13
Becky = 10
Candy = 6
Daisy = 99
Eliza = 9

 基本的には他のオブジェクトも同じ考え方でできるので、色々やってみると良いだろう。


(関連記事)
【Java】2次元配列のソート
【Java】連想配列(Map)を foreach(for, forEach) で取り出す
【Java】連想配列(Map)を値でソートする
【Java】配列要素の反転(reverse)
【Android】【Java】SparseArray で foreach


category: Java

thread: プログラミング

janre: コンピュータ

tag: 配列操作  連想配列 
tb: 0   cm: --

【Java】連想配列(Map)を値でソートする  


 キーでのソート(昇順)は TreeMap を使えば勝手にやってくれる。しかし集計など、値でのソートをしたいことも多い。その場合はリストなどを中継し、Comparator で並び変えると良い。

●連想配列(Map)を値でソート(昇順/降順)
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

//初期化
Map<String, Integer> map = new HashMap<String, Integer>(){ //TreeMap でも良い
{
put("Alice", 13);
put("Becky", 10);
put("Candy", 6);
put("Daisy", 99);
put("Eliza", 9);
}
};

//Map.Entry のリストを作る
List<Entry<String, Integer>> entries = new ArrayList<Entry<String, Integer>>(map.entrySet());

//Comparator で Map.Entry の値を比較
Collections.sort(entries, new Comparator<Entry<String, Integer>>() {
//比較関数
@Override
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
return o1.getValue().compareTo(o2.getValue()); //昇順
//return o2.getValue().compareTo(o1.getValue()); //降順
}
});

//確認用
for (Entry<String, Integer> e : entries) {
System.out.println(e.getKey() + " = " + e.getValue());
}

//Java 8 なら...
//entries.forEach(e -> System.out.println(e.getKey() + " = " + e.getValue()));


 構造としてはキーと値のペア(Map.Entry)のリストを作り、そのリストをソートしている感じ。要素はオブジェクトの参照なので、キーでも値でも取り出せる。この例では Comparator を匿名クラスで作ってるが、「2次元配列のソート」のときのように、独立したクラスとして定義して置いて、フラグで昇順・降順を切り替えるのも良いだろう。


 キーを逆順(降順)ソートしたい場合は、同じような考え方でできる。

●連想配列(Map)をキーで降順ソート
//キーのリストを作る
List<String> keylist = new ArrayList<String>(map.keySet());

//Comparator でキーを降順ソート
Collections.sort(keylist, new Comparator<String>() {
//比較関数
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});

//確認用
for (String key : keylist) {
System.out.println(key + " = " + map.get(key));
}

//Java 8 なら...
//keylist.forEach(e -> System.out.println(e + " = " + map.get(e)));


 昇順なら Collections.sort(keylist) だけで良いね(TreeMap なら必要ない)。いずれにしても連想配列は順番(連番)という概念はあまりないので、List などを間に挟むと、色々操作しやすい。


(関連記事)
【Java】連想配列(Map)を foreach(for, forEach) で取り出す
【Java】配列, リスト(List), 連想配列(Map) の初期化
【Android】【Java】SparseArray で foreach
【Java】2次元配列のソート
【Java】配列要素の反転(reverse)


category: Java

thread: プログラミング

janre: コンピュータ

tag: 連想配列  配列操作  ソート 
tb: 0   cm: --

【Android】【Java】SparseArray で foreach  


 ADV システムっぽいのを作っていると、IDと値(文字列やオブジェクト)という組み合わせを使うことが多い。IDが連番のものなら普通の配列を使うことも多いんだけど、IDが飛び番号できるようにするには連想配列を使う。ただ、連想配列にはいくつか種類があるので、用途で使い分けるのが良いかもしれない。IDが数値の場合は、ソートされたリストを取得するには TreeMap を、ソートの必要なく、put() のパフォーマンスが欲しいときには HashMap を使うとかね。で、その HashMap を使おうと思ったら、「キーが int 型なら、SparseArray の方がパフォーマンス良いよ」みたいな警告が Eclipse で出た。

 ググってみたら、get() のパフォーマンス HashMap > SparseArray、put() のパフォーマンス SparseArray > HashMap らしい。

(参考) AndroidのSparseArrayは本当に速いのか測定してみた

 もうひとつ気になったのは、やはりどういう順番で値が出てくるかなのでやってみた。

SparseArray<String> data = new SparseArray<String>();

data.put( 5, "value5");
data.put( 12, "value12");
data.put( 1, "value1");
data.put(100, "value100");

for (int i = 0; i < data.size(); i++) {
int key = data.keyAt(i);
String val = data.valueAt(i);
}

 結果は数値の順に出てくる。

key = 1 / val = value1
key = 5 / val = value5
key = 12 / val = value12
key = 100 / val = value100

 ついでに、TreeMap とのコード比較。

Map<Integer, String> data = new TreeMap<Integer, String>();

data.put( 5, "value5");
data.put( 12, "value12");
data.put( 1, "value1");
data.put(100, "value100");

for (Map.Entry<Integer, String> e : data.entrySet()) {
int key = e.getKey();
String val = e.getValue();
}

 結果的には同じ。見易さもコードの長さも大差ない。これもパフォーマンスなど用途によって使い分けるのが良いのかな。ただし、SparseArray は Android 用 API だけどね。


(関連記事)
【Java】連想配列(Map)を foreach(for, forEach) で取り出す
【Java】連想配列(Map)を値でソートする


category: Android

thread: プログラミング

janre: コンピュータ

tag: 連想配列  配列操作 
tb: 0   cm: --

【Java】連想配列(Map)を foreach(for, forEach) で取り出す  


 PHP や Perl に慣れてると Java の連想配列は使いづらい気がするね。データを外部化したとき、キーと値のセットでデータを作成しておいたので、読み込むときには連想配列を使っている。全ての値を取り出すには Iterator を使う手もあるけど、Map.Entry を使うとわりと foreach っぽく表記できるね。

import java.util.HashMap;
import java.util.Map;

Map<String, String> hash = new HashMap<String, String>();

//適当に代入
hash.put("key1", "value1");
hash.put("key3", "value3"); //わざと3
hash.put("key2", "value2"); //わざと2

//キーと値
System.out.println("[key = val]");
for (Map.Entry<String, String> e : hash.entrySet()) {
String key = e.getKey();
String val = e.getValue();
System.out.println(key + " = " + val);
}

//キーのみ
System.out.println("[keys]");
for (String e : hash.keySet()) {
System.out.println(e);
}

//値のみ
System.out.println("[values]");
for (String e : hash.values()) {
System.out.println(e);
}

[key = val]
key1 = value1
key2 = value2
key3 = value3
[keys]
key1
key2
key3
[values]
value1
value2
value3

 また、Java8 ではラムダ式が使えるようになったので、forEach() で簡潔に書くこともできる。

//※定義は略(↑のものと同じ)
//キーと値
hash.forEach((key, val) -> System.out.println(key + " = " + val)); //Java8

key1 = value1
key2 = value2
key3 = value3

 ちなみに HashMapTreeMapLinkedHashMap に代えてもそのまま使える。これらの連想配列は保持しているデータの順番が違うらしい。上記のコードでそれぞれクラスを変えてみると、

HashMap:順不同
TreeMap:昇順
LinkedHashMap:追加した順

に値が出てくる(文字列順)。順を考えなければ何でも良いみたい。Java って同じような機能多いから、初めは迷うね。StringBufferStringBuilder とか。実行速度やマルチスレッドプログラミングかなどで変えた方が良いみたいだけどね。


(関連記事)
【Android】SparseArray で foreach
【Java】配列, リスト(List), 連想配列(Map) の初期化
【Java】連想配列(Map)を値でソートする


category: Java

thread: プログラミング

janre: コンピュータ

tag: 連想配列  配列操作 
tb: 0   cm: --
IS<インフィニット・ストラトス> アーキタイプ・ブレイカー
キルドヤ


プロフィール

Twitter

検索フォーム

全記事一覧

カテゴリ

ユーザータグ

最新記事

リンク

PR

▲ Pagetop