【Java】配列要素の反転(reverse) 
2015/04/09 Thu [edit]
List の要素の操作は Collections.reverse() でできるのだが、プリミティブ型の配列の場合、Arrays を使うので、いくつか欲しい機能が無かったりするね。
reverse() もそのうちの1つなのだが、自作するのも簡単なので、2次元配列版も含めて作ってみた。
●1次元配列の要素を反転する
/**<h1>1次元配列の要素を反転する</h1>
* <p></p>
* @param arr : 対象配列
*/
public static final void reverse(int[] arr) {
final int len = arr.length;
int tmp;
for (int i = 0; i < len / 2; i++) {
tmp = arr[i];
arr[i] = arr[len - 1 - i];
arr[len - 1 - i] = tmp;
}
}

配列の最初と最後の方から交換して、真ん中まで来たら終わりというアルゴリズム。一番わかりやすくて良いね。検索してたら再帰を使って反転するなんてものもあったが、練習問題としては良いが、実際に使わない方がいいね。ソートのときにも書いたが、再帰はメモリを喰うので、携帯やスマホみたいなメモリが小さい端末にはあまり良くない。よく再帰アルゴリズムの例として「階乗の計算」が挙げられるが、あれも実際には使わないしね。私はいつも単純明快でいきますよ(笑)。
(参考) Javaで配列の逆順表示をやってみよう
(参考) 再帰アルゴリズム
2次元の場合は横方向(列の入れ替え)と縦方向(行の入れ替え)が考えられるので、2通り用意した。
●2次元配列の要素を反転する(横方向)
/**<h1>2次元配列の要素を反転する(横方向)</h1>
* <p>col 方向の反転(arr[row][col])。長さの違う配列は基本的に不可。</p>
* @param arr : 対象配列
*/
public static final void reverseCol(int[][] arr) {
final int col = arr[0].length;
final int row = arr.length;
int i, j, tmp;
for (i = 0; i < col / 2; i++) {
for (j = 0; j < row; j++) {
tmp = arr[j][i];
arr[j][i] = arr[j][col - 1 - i];
arr[j][col - 1 - i] = tmp;
}
}
}

ソートのときと同じで、入れ替えを列での処理にしただけ。
ただ注意点としては、各行の長さが違う配列には対応してないことだ。特に例外処理は設けてないので、必要なら「ArrayIndexOutOfBoundsException」を catch か throws するようにした方が良いだろう。
●2次元配列の要素を反転する(縦方向)
/**<h1>2次元配列の要素を反転する(縦方向)</h1>
* <p>row 方向の反転(arr[row][col])。</p>
* @param arr : 対象配列
*/
public static final void reverseRow(int[][] arr) {
final int row = arr.length;
int[] tmp;
for (int i = 0; i < row / 2; i++) {
tmp = arr[i]; //行は配列の配列(ネスト)なので、行ごと入れ替えればいい
arr[i] = arr[row - 1 - i];
arr[row - 1 - i] = tmp;
}
}

縦方向の場合は、行をまるごと入れ替えているようなものなので(参照を入れ替えている)、各行の長さが違う配列でも構わない。Java の配列ってネストしてるんだよね。List を反転する Collections.reverse() と同じ結果となる。
■Collections を用いた List の reverse
比較用に List での reverse も書いておく。といっても簡単な使い方と結果だけ。
●1次元のリスト(List)の要素を反転する
//初期値を定義
@SuppressWarnings("serial")
List<Integer> list = new ArrayList<Integer>(){
{
add(1); add(2); add(3); add(4); add(5);
}
};
Collections.reverse(list); //要素を反転
//確認用
for (Integer i : list) {
System.out.print(i + " ");
}
System.out.println();

●2次元のリスト(List のネスト)の要素を反転する(縦方向)
//初期値を定義
@SuppressWarnings("serial")
List<List<Integer>> list = new ArrayList<List<Integer>>(){{
add(new ArrayList<Integer>(){{
add(1); add(2); add(3); add(4); add(5);
}});
add(new ArrayList<Integer>(){{
add(5); add(4); add(3); add(2); add(1);
}});
add(new ArrayList(){{
add(4); add(1); add(5); add(3); add(2);
}});
}};
Collections.reverse(list); //要素を反転
//確認用
for (List<Integer> li : list) {
for (Integer i : li) {
System.out.print(i + " ");
}
System.out.println();
}

「@SuppressWarnings("serial")」は警告を消しているだけなので、無くても動く。初期化は無理矢理やってるが、単純に list.add() で追加しても良い。ただし、2次元のリストは入れ子で new してオブジェクトを作らないといけないので注意(この例の場合、ArrayList の中に ArrayList を作ってる感じ)。
2次元のリストの場合、この例では reverse は縦方向の反転になるが、横方向でやりたければ、各要素を取り出して「Collections.reverse(list.get(i))」のようにすれば良い。ただし、各行の長さ(要素数)が違う場合は、意図した並びにならない可能性もあるので注意。
プリミティブ型の配列と List の使い分けは静的配列か動的配列かという感じだが、長さが変わらないのであれば、プリミティブ型の配列の方が処理は速い。特に要素数が増えたときは List はどんどん重くなっていくので、その辺を考慮に入れるのもいいだろう。「ArrayList は get() が速く、LinkedList は add(), remove() が速い」という話もあるが、まぁ、試してみるのが一番だろう。
余談だが、並び替えには値のスワップをよく使うので、CやC++みたいな swap() 関数みたいのないかな~、と検索してたら、色々やってるので笑ってしまった。Java にはポインタはないからね。一時的に配列を使うとか Point クラスを使うとか、何かを中継しているものが多かったが、XOR 演算なんかも「なるほどな~」と思った。でも元のコード(一時変数を使うコード)よりも長かったり、演算をしてたりすると処理に時間がかかるし、オブジェクトを頻繁に new するとガベージコレクタも動きまくるし、メモリも喰うので、やっぱり使えないな~と思った。何をやっているのかわかりにくいというのも、長い目で見れば意外と重要だしね(後から改造しにくい)。色々な言語をやってると「これあったらいいのに…」と思えるものがよく出てくるので、そういうのをあらかじめ作って置くと便利かもね。
(参考) 一時変数を使わずスワップする方法
(参考) How to write a basic swap function in Java (stackoverflow)
(関連記事)
【Java】2次元配列のソート
【Java】連想配列(Map)を値でソートする
【Java】配列, リスト(List), 連想配列(Map) の初期化
- 関連記事
トラックバック
トラックバックURL
→http://fantom1x.blog130.fc2.com/tb.php/172-328d59f4
この記事にトラックバックする(FC2ブログユーザー)
| h o m e |