2차원 배열은 행과 열로 찾는다
앞 글의 배열은 칸이 한 줄이었습니다. 그런데 다루려는 것이 원래 격자인 경우가 있습니다. 바둑판, 지도, 표 같은 것들입니다.
한 줄짜리 배열로도 할 수는 있습니다. 3 × 3 판이면 9칸짜리 배열을 만들고 직접 계산해서 쓰면 됩니다.
int[] board = new int[9];
board[1 * 3 + 2] = 1; // 1행 2열
돌아가긴 합니다. 다만 1 * 3 + 2 를 매번 손으로 적어야 하고,
판 크기가 바뀌면 곱하는 숫자를 전부 고쳐야 합니다.
번호 두 개로 칸을 가리키게 해 주는 것이 2차원 배열입니다.
2차원 배열은 이렇게 생겼다
int[,] board = new int[3, 3];
board[1, 2] = 1;
Console.WriteLine(board[1, 2]); // 1
대괄호 안에 쉼표가 하나 들어갔습니다. 이게 “번호를 두 개 받는다” 는 표시입니다.
new int[3, 3] 은 3행 3열, 그러니까 아홉 칸입니다.
값을 미리 적을 때는 중괄호를 겹쳐 씁니다.
int[,] board =
{
{ 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 1 },
};
적은 모양이 그대로 판 모양입니다. 첫 번호가 행(줄), 둘째 번호가 열(칸)입니다.
격자로 보이지만 실제로는 한 줄이다
여기가 이 글에서 짚고 갈 부분입니다.
메모리는 격자가 아닙니다. 앞에서 본 대로 주소가 0번부터 쭉 이어지는 한 줄입니다. 2차원 배열도 거기에 놓입니다.
한 행을 다 놓고 나서 다음 행을 이어 놓습니다.
그래서 board[행, 열] 은 결국 행 × 열개수 + 열 번째 칸입니다.
맨 앞에서 우리가 손으로 계산했던 1 * 3 + 2 를 컴파일러가 대신 해 주는 것입니다.
문법이 바뀐 것이지 메모리가 격자가 된 것은 아닙니다.
이중 반복문으로 전부 훑는다
앞에서 바깥이 행, 안쪽이 열이라고 했습니다. 여기가 그 자리입니다.
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
Console.Write(board[row, col] + " ");
}
Console.WriteLine();
}
별찍기에서 Write 로 이어 찍고 WriteLine() 으로 줄을 바꿨던 것과 같습니다.
안쪽이 한 줄을 다 찍고, 바깥이 줄을 넘깁니다.
행과 열의 개수는 GetLength 로 받습니다.
board.GetLength(0) // 행 개수
board.GetLength(1) // 열 개수
Length 는 전체 칸 수(9)를 줍니다. 반복문 조건에 그냥 쓰면 안 됩니다.
순서를 바꾸면 느려질 수 있다
두 반복문의 순서를 바꿔도 결과는 같습니다. 아홉 칸을 전부 방문하는 것은 똑같으니까요.
for (int col = 0; col < 3; col++)
{
for (int row = 0; row < 3; row++)
{
Console.Write(board[row, col] + " "); // 열 방향으로 훑습니다
}
}
3 × 3 이면 차이가 없습니다. 그런데 판이 커지면 이야기가 달라집니다.
메모리에는 행 순서로 놓여 있습니다. 행 방향으로 훑으면 바로 옆 칸으로 가고, 열 방향으로 훑으면 매번 한 행 크기만큼 건너뜁니다. CPU 는 값 하나를 읽을 때 그 주변을 한 덩어리로 미리 가져다 두는데, 멀리 뛰면 그 덩어리가 매번 헛것이 됩니다.
방문한 칸 수는 같은데 건너뛴 횟수가 다릅니다. 행 우선은 열한 번 전부 바로 옆 칸이고, 열 우선은 열한 번 전부 건너뜁니다. 격자에서는 똑같이 한 칸씩 옮겨 다니는데 메모리에서는 그렇지 않습니다.
지금 단계에서 외울 것은 아닙니다. 다만 격자로 보여도 아래는 한 줄이라는 사실이 성능에 드러나는 자리가 있다는 것 정도는 알아두면 됩니다.
칸마다 길이가 다른 격자
행마다 칸 수가 다른 경우가 있습니다. 이때는 모양이 다릅니다.
int[][] jagged = new int[3][];
jagged[0] = new int[2];
jagged[1] = new int[5];
jagged[2] = new int[1];
jagged[1][3] = 7; // 대괄호가 두 벌입니다
int[,] 는 대괄호 한 벌에 쉼표, int[][] 는 대괄호 두 벌입니다.
생김새가 비슷해서 헷갈리는데 안이 완전히 다릅니다.
int[,] 는 통짜 한 줄이고, int[][] 는 배열을 담는 배열입니다.
바깥 배열의 칸마다 다른 배열의 주소가 들어 있습니다.
그래서 행마다 길이를 다르게 만들 수 있고, 대신 칸들이 한 줄로 붙어 있지 않습니다.
대부분은 int[,] 로 충분합니다.
정리
int[,]는 번호 두 개로 칸을 가리키는 배열입니다. 첫 번호가 행, 둘째가 열입니다.- 격자로 보이지만 메모리에는 한 행씩 이어 붙여 한 줄로 놓입니다.
board[행, 열]은행 × 열개수 + 열번째 칸입니다.- 전부 훑을 때는 이중 반복문을 씁니다. 바깥이 행, 안쪽이 열입니다.
- 행 개수는
GetLength(0), 열 개수는GetLength(1)입니다.Length는 전체 칸 수입니다. - 행 방향으로 훑는 편이 메모리에 놓인 순서와 맞습니다.
- 행마다 길이가 다르면
int[][]입니다. 배열을 담는 배열이라 안이 다릅니다.