雑草SEの備忘録

東大を卒業し、SEとして働くことになった。備忘録的に作業を綴る。

JavaのComparatorを使って配列を並び替える

Javaで配列の並び替えをしようと思った時に少し慣れるのに時間がかかったので、書いておきたいと思います。

配列の並び替えには、

Arrays.sort(配列名);

みたいな感じでやれば昇順に並べ替えられます。Listとかだと、Collection.sort()なんてのを使うんだと思います。なんですが、ここの配列名のところで指定できる配列は、intとかcharとか基本データ型の配列です。

もし、何かのオブジェクトを入れたいのであれば、実装方法は二つあります。

1.配列のオブジェクトに、Comparableインタフェイスを実装する

2.配列のオブジェクトはそのままで、Comparatorを用いる(後述)

の二つあると思います。今回は、2のやり方についてのみ記載します。(1はまだ知らん。。)

Comparatorの使い方については、いろいろググりましたが、無駄にラムダ式で記載されていたり、なぜか-1とかが使われていたり、ちょっと私には分かりにくかったです。

というわけで、自分で説明してみようと思います。

0.説明の準備
1.年齢順に並べる
2.文字順で並び替え
3.年齢順、あいうえお順で並び替え
4.性別、年齢、名前の順に昇順に並び替える
5.課題


0.説明の準備
まずは、基本データ型の配列ではなくて、あるエンティティの配列で並び替えを行いたいと思うので、そのエンティティを用意します。今回は、Student型のエンティティで説明します。

public class Student {
	// 年齢
	private int age;
	// 身長
	private int height;
	// 名前
	private String name;
	// 性別
	private char sex;
	// コンストラクタ
	public Student(String name, int age,  char sex, int height){
		this.age = age;
		this.height = height;
		this.name = name;
		this.sex = sex;
	}
	// 以下、ゲッターとセッター
	public int getAge(){ return age; }
	public void setAge(int age){ this.age = age; }
	public int getHeight() { return height; }
	public void setHeight(int height) { this.height = height; }
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }
	public char getSex() { return sex; }
	public void setSex(char sex) { this.sex = sex; }
}

次に、mainメソッドのクラス(名前はなんでもいいが、昔エネルギーの研究をしていたということもあり、SmartEnergyを使ってみた)をつくり、そこに配列を用意します。ついでに、配列を表示させるSystem.out.println()もつけておきます。☆☆☆のところは、あとから、配列を並び替えるために記述する箇所です。

public class SmartEnergy {
	public static void main(String[] args) {
		Student[] studentarray = new Student[6];
		studentarray[0] = new Student("いのうえなおみ", 24, 'f', 158);
		studentarray[1] = new Student("たかはしたつや", 23, 'm', 176);
		studentarray[2] = new Student("いのうえなおみ", 27, 'm', 164);
		studentarray[3] = new Student("えのもとめぐみ", 24, 'f', 162);
		studentarray[4] = new Student("えのもとたすく", 24, 'm', 178);
		studentarray[5] = new Student("さくらぎたかはる", 26, 'f', 163);

		☆☆☆

		for (Student student : studentarray) {
			System.out.println(student.getName() + "," + student.getAge() + ","
					+ student.getSex() + "," + student.getHeight());
		}

	}
}


1.年齢順に並べる
例えば、年齢順に並べるには、次のようにします。☆☆☆の部分に挿入してください。

(記述例1)
Arrays.sort(studentarray, new Comparator<Student>() {
	public int compare(Student student1, Student student2) {
		return student1.getAge() - student2.getAge();
	}
});

Arrays.sortでは、一つ目の引数に、並び替えたい配列を、二つ目の引数には、並び替え方を記述したComparatorを実装したクラスを入れます。今回は、二つ目の引数は、インナークラスとしています。
新たにクラスを作成して、以下のように記述しても構いません。
まずは、☆☆☆のところに以下の一文を記述。SampleComparatorとは、すぐあとで作るComparatorを実装したクラスです。

(記述例2)
Arrays.sort(studentarray, new SampleComparator());

次に、新しいクラスを作りSampleComparatorと名付けることにします。このクラスには、Comparatorを実装します。

(記述例2つづき)
class SampleComparator implements Comparator<Student>{
	public int compare(Student student1, Student student2) {
		return student1.getAge() - student2.getAge();
	}
}

ちなみに、ComparatorのジェネリクスをStudentにしていますが、これは並び替えの対象がStudentだからです。ジェネリクスを指定しなくても以下のように書けばコンパイルはできますし、正常に動作します。

(記述例3:推奨しない)
Arrays.sort(studentarray, new Comparator() {
	public int compare(Object student1, Object student2) {
		return ((Student)student1).getAge() - ((Student)student2).getAge();
	}
});

なんですが、Eclipseを使ってると、色々と警告がでます。単純にダウンキャストを使ってるので未検査のダウンキャストかと思いきや、そうじゃないみたいです。中身はよく分かりませんが、この書き方は推奨しないよということみたいです。Java1.4まではこれで良かったんだそうな。。(昔はジェネリクスという考え方が無かった)
これらの実装をして、mainメソッドのあるクラス(私の場合SmartEnergy)を実行すると、

(実行例1)
たかはしたつや,23,m,176
いのうえなおみ,24,f,158
えのもとめぐみ,24,f,162
えのもとたすく,24,m,178
さくらぎたかはる,26,f,163
いのうえなおみ,27,m,164

といったのがコンソールに表示されると思います。確かに、年齢順に並び替えられています!素晴らしい!

さて、並び替えのルールはこのComparatorの中に記載されています。
一番最初の(記述例1)に戻って下さい。returnのところで、student1の年齢からstudent2の年齢を引いたものを返却しています。student1の年齢の方が大きい場合が正になりますから、結果をみると、student1の年齢がstudent2の年齢よりも大きい時に正を返す場合は、年齢で昇順に並び替わるというわけですね。
ためしに、returnの返却値をstudent2.getAge() - student1.getAge()とすると、年齢で降順(年齢が大きい順)に並び替わります。
正直、このあたり、どっちが昇順でどっちが降順なのかわからなくなります。こういうときは私は、ためしに小さいプログラムを組んで試してみています。
基本的にComparatorの使い方は以上ですが、続いて文字で並び替えてみましょう。

2.文字順で並び替え
あいうえお順で並び替えるときはStringのcompareTo()メソッドを使います。次のコードを☆☆☆に書き加えてください。(1.で☆☆☆に挿入したのは一旦削除してください)

Arrays.sort(studentarray, new Comparator<Student>() {
	public int compare(Student student1, Student student2) {
		return student1.getName().compareTo((student2.getName()));
	}
});

実行して、次のような画面が出ていればOKです。あいうえお順に並び替えることが出来ました。

(実行例2)
いのうえなおみ,24,f,158
いのうえなおみ,27,m,164
えのもとたすく,24,m,178
えのもとめぐみ,24,f,162
さくらぎたかはる,26,f,163
たかはしたつや,23,m,176

ただし、注意しなければいけないのは、StringのcompareTo()メソッドはUNICODE的な順番を比較しています。ひらがなであれば問題ないですが、漢字だと上手くいかない可能性が高いです。


3.年齢順、あいうえお順で並び替え
それでは、年齢順に並べつつ、かつ、同じ年齢の場合はあいうえお順に並べてみましょう。(実行例1)では、「えのもとめぐみ」のあとに「えのもとたすく」となっており、あいうえお順ではありません。
年齢とあいうえお順で並び替えるには、まず、年齢で値を比較し(年齢なので差をとる)、ゼロであれば(同じ年齢であれば)、次にあいうえお順に並べるという手順を踏みます。

Arrays.sort(studentarray, new Comparator<Student>() {
	public int compare(Student student1, Student student2) {
		int temp = student1.getAge() - student2.getAge();
		if (temp == 0){
			return student1.getName().compareTo(student2.getName());
		}
		return temp;
	}
});

上の例ですと、student1の年齢からstudent2の年齢を引いた値をtempに代入します。tempがゼロであれば、同じ年齢であるということなので、今度は、あいうえお順で比較(compareTo()メソッド)し、それを返却するひょうにしています。実行すると次のような結果が出ると思います。年齢順に並んでいますし、「えのもとたすく」が「えのもとめぐみ」より前に来ているのが分かりますね。

(実行例3)
たかはしたつや,23,m,176
いのうえなおみ,24,f,158
えのもとたすく,24,m,178
えのもとめぐみ,24,f,162
さくらぎたかはる,26,f,163
いのうえなおみ,27,m,164


4.性別、年齢、名前の順に昇順に並び替える
もう少し応用して、条件を3つにしてみましょう。男性(m)を先に、女性(f)を後に並び替えるようにするにはどうすれば良いでしょうか。
今回の例では、性別に'f'や'm'といったchar型を用いていますので単純に大小比較ができます。ですが、練習問題ではLGBTの方も勘案して、性別に'x'というものを加えて、LGBT→女性→男性の順に表示させる問題を出しています。今回は、char型の大小比較を使わないで実装してみましょう。

Arrays.sort(studentarray, new Comparator<Student>() {
	public int compare(Student student1, Student student2) {
		int temp = 0;
		if (student1.getSex() == 'm' && student2.getSex() == 'f')
			temp = -1;
		if (student1.getSex() == 'f' && student2.getSex() == 'm')
			temp = 1;
		if (temp == 0) {
			temp = student1.getAge() - student2.getAge();
			if (temp == 0) 
				temp = student1.getName().compareTo(student2.getName());
		}
		return temp;
	}
});

student1の性が'm'かつstudent2の性が'f'のときはtempに-1(負の数)を、student1の性が'f'かつstudent2の性が'm'のときはtempに1(正の数)を代入しています。それ以外(student1とstudent2の性が同じ)のときは、tempはゼロですので、その時は、まず年齢比較、次いで、あいうえお比較をしています。
これを実行すると、次のような結果が得られます。

たかはしたつや,23,m,176
えのもとたすく,24,m,178
いのうえなおみ,27,m,164
いのうえなおみ,24,f,158
えのもとめぐみ,24,f,162
さくらぎたかはる,26,f,163


5.課題
ここまでざっと説明をしてきました。説明だけでは理解が不十分だと思う方は、次の課題をやってみてください。
問1.配列studentarrayを身長順に並べよ。
問2.配列studentarrayを年齢順に並べよ。年齢が同じ場合は、身長順に並べること。
問3.配列studentarrayを名前順に並べよ。名前が同じ場合は、身長順に並べること。
問4.配列studentarrayのサイズを7にし、以下のを追加する。
   studentarray[6] = new Student("くらもとますみ", 26, 'x', 168)
   この状態で、性別が'x'→'f'→'m'の順にならべよ。同じ性別内では、年齢順に、同じ年齢であれば名前順(あいうえお順)に並べよ。

以上、「JavaのComparatorを使って配列を並び替える」でした。久しぶりにSE的なことを書いたので時間もかかったしつかれかました。課題の答えはあとで時間があればアップします。

2015/05/04 追記
解答アップしました!
normalse.hatenablog.jp