Javaのsplitと多次元配列で沼ったお話

# この記事の内容
Javaのsplitを二次元配列で行った場合に発生する事象について備忘録的にまとめました。

## この記事を書いた人
- 20歳専門学生(情報系)
- Java歴4年くらいで割とJavaには自身ある方だった
- Atcoder数回やっただけの人(数学よくわからない人)
- PaizaはBランク
- 競技プログラミングを久しぶりにやっている人

# 事の発端
Paizaとかで競技プログラミングやってみるか、となりまして。
今回のプログラムは入力値がnとmであることが保証された問題だったので、入力値を二次元配列Map上に保持しておいていろいろする、という誰でもできるプログラムで沼りました。。。本当にびっくり仰天でした。

```Main.java
import java.util.*;
public class Main {
    public static void main(String[] args) {

        Scanner scn = new Scanner(System.in);
        int n = Integer.parseInt(scn.next());
        int m = Integer.parseInt(scn.next());
        String[][] map = new String[n + 2][m + 2];

        for(int i = 0; i < n; i++){
            String str = scn.next();
            //==================ここが怪しい======================
            map[i] = str.split("");
            map[i][m] = "#";
            map[i][m + 1] = "#";
            //===================================================
        }

        for(int i = n; i < n + 2;i++){
            for(int j = 0;j < m + 2;j++){
                map[i][m] = "#";
            }
        }

        System.out.println(map.length);
        for(int i = 0; i < n + 2; i++){
            for(int j = 0; j < m + 2; j++){
                System.out.print(map[i][j]);
            }
            System.out.println();
        }
    }
}
```

```:入力値

7 7
#..#..#
#.###..
...#...
####...
.....##
..##...
#...##.

```

```:出力結果(エラー)
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7 at Main.main(Main.java:13)
```

一応、実行結果の想定です。
```
#..#..###
#.###..##
...#...##
####...##
.....####
..##...##
#...##.##
#########
#########
```
このように、mapのlengthが7,7に対して新たに2行と2列を追加して'#'を挿入するプログラムです。(これだけでどんな問題だかわかりそうではある)

# 原因
今回のエラーの原因は二次元配列の列数(7 + 2)が何故か7のままで初期化されてしまったことが問題です。今回のプログラムの原因は大きく以下が考えられます。

- 何らかの原因でString型二次元配列の初期化が未定義にされてしまっている。
- splitの仕様上入力値で配列を初期化している。
- 僕のミス(ここで2時間使ってたのでそれは考えたくない)

以上から今回はsplitの可能性を考慮します。


以下のようなソースを用意しました。
``` Main.java
import java.util.Scanner;
public class Main{
    public static void main(String[] args){
        Scanner scn = new Scanner(System.in);
        String[] arr = new String[100];    //一次元配列を100で初期化
        String str = scn.next();
        arr = str.split("");

        System.out.println(arr.length)   // <- aiueokakikukekoの場合、15と表示されるはず

        for(int i = 0; i < 100; i++){
            System.out.print(arr[i] + ",");
        }
    }
}
```
```:入力値
aiueokakikukeko
```
```:出力結果
a,i,u,e,o,k,a,k,i,k,u,k,e,k,o,
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 15 out of bounds for length 15
at Main.main(Main.java:10)
```

このようにString型一次元配列の要素数を100としても実際に配列で定義された要素数は15になります。


# 解決策
今回の問題点は以下になります。

- splitするとその区切られた配列の要素数で初期化されてしまう
- 二次元配列だと列に対してsplitを行うと1000%エラーが出る

## 案1 : map[i]の要素数を直接参照する(失敗)
```Main.java
import java.util.*;
public class Main {
    public static void main(String[] args) {

        Scanner scn = new Scanner(System.in);
        int n = Integer.parseInt(scn.next());
        int m = Integer.parseInt(scn.next());
        String[][] map = new String[n + 2][m + 2];

        for(int i = 0; i < n; i++){
            String str = scn.next();
            map[i] = str.split("");
            map[i][map[i].length - 1] = "#";
            map[i][map[i].length] = "#";
        }

        for(int i = n; i < map.length;i++){
            for(int j = 0;j < map[i].length;j++){
                map[i][j] = "#";
            }
        }

        System.out.println(map.length);
        for(int i = 0; i < map.length; i++){
            for(int j = 0; j < map[i].length; j++){
                System.out.print(map[i][j]);
            }
            System.out.println();
        }
    }
}
```

これでもエラーが起きました。おそらく

```
            map[i][map[i].length - 1] = "#";
            map[i][map[i].length] = "#";
```

の時点でindexOutOfBounds君が発生するからです。なので次の案を出します。


## 案2 : 一度splitを別配列にしてからそれを順次挿入する

```Main.java
import java.util.*;
public class Main {
    public static void main(String[] args) {
        //前処理
        Scanner scn = new Scanner(System.in);
        int n = Integer.parseInt(scn.next());
        int m = Integer.parseInt(scn.next());
        String[][] map = new String[n + 2][m + 2];
        //挿入処理
        for(int i = 0; i < n; i++){
            String str = scn.next();
            String[] memo = str.split("",0);
            for(int j = 0;j < m;j++){
                map[i][j] = memo[j];
            }
            map[i][m] = "#";
            map[i][m + 1] = "#";
        }
        for(int i = n; i < map.length;i++){
            for(int j = 0;j < map[i].length;j++){
                map[i][j] = "#";
            }
        }
        //結果
        System.out.println(map.length);
        for(int i = 0; i < map.length; i++){
            for(int j = 0; j < map[i].length; j++){
                System.out.print(map[i][j]);
            }
            System.out.println();
        }
    }
}

```

```:出力結果
9
#..#..###
#.###..##
...#...##
####...##
.....####
..##...##
#...##.##
#########
#########
```
ちゃんと初期値が9に設定されたままでエラーを起こさずに実行できました。

# まとめ

splitを多次元配列上に行う場合は一度別の一次元配列にsplitをしてから行うようにしましょう。
恐らくJavaの仕様(?)っぽいです

# おわりに
今回マジでキレ散らかしながらこの記事書いてました。初めてなのにこの記事は少々イカれてます。何せ、javaを四年間使ってきてこんなんでミスるのがびっくりでした。

#### 当時の僕
>「どうしてなの...!!!」
>「なんでなの...ﾜ...!!!」
>「なんで動かないの...!!!」
>「ｳｩ...ウゥゥ...」「ﾜ...泣いちゃった...!」

と若干ちい〇わみたいな独り言を2時間していたので疲労感半端ないです。まあプログラマーなんてちい〇わの集いみたいな集団ですので。勉強します。

あ、僕一応GithubとかPaizaとかAtcoderとかなんかいろいろとやっている微妙な人間なのでよかったらよろしくお願いします。アドバイスなどもよろしくお願いします。