자바의 정석

SUADI·2022년 6월 5일

5장 - 배열(Array)

1.4 배열의 출력

배열을 반환하는 방법 에는 여러가지가 있다. 반복문을 이용하는 방법이 있고, 반환을 해주는 메소드를 이용할 수 도 있다. 반복문을 이용하는 방법은 알고 있으므로 반환해주는 메소드를 포스팅하려고 한다. 배열을 반환해주는 메소드는 Array.toString이다. 메소드를 숙지하는 것은 한두번 사용해 본다고 머릿 속에 들어오지 않아서 볼때마다 다시 한번 되새기고 공부해야 할 것 같다.

int[] num = {1,2,3,4,5,6,7,8,9};
System.out.println(Arrays.toString(num));
// [1, 2, 3, 4, 5, 6, 7, 8, 9] 출력

1.5 배열의 복사

배열을 복사하는 방법에도 두가지 방법이 있다. 이것 역시 반복문을 사용할 수도 있고, 메소드를 사용할 수도 있다. 먼저 for문을 사용해서 저장되어 있던 배열을 새로운 배열로 복사해 보겠다.

        int[] num = {1,2,3,4,5,6,7,8,9};
        int[] newNum = new int[num.length * 2];
        for (int i=0; i< num.length; i++) {
            newNum[i] = num[i];
        }
        num = newNum;
        System.out.println(Arrays.toString(newNum));
        // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  • 새로운 배열 newNum에 기존 배열 num을 복사해보려고 한다. newNum을 기존 배열의 두배 크기로 만든 이유는 프로그램 작성 중 배열의 크기가 부족해서 새로운 배열로 이동해야 하는 경우를 고려해서 2배 정도 크게 만들었다. 크기가 기존배열보다 크기만 하면 복사가 가능하다. 기존의 배열 크기만큼의 for문을 작성한 후, newNum에 기존 배열을 저장하고, for 문을 빠져나와 num참조변수에 newNum참조변수를 저장한다. 이렇게 하면 참조변수 num과 newNum은 같은 배열을 가리키게 된다. num = newNum을 하는 이유는 같은 배열을 가르키게 되어 쓸모가 없어진 배열은 JVM의 가비지 컬렉터에 의해 자동적으로 메모리에서 제거된다. 쓸모없는 메모리를 줄여주기 위함이라고 볼 수 있다. 참조변수에 대해서는 객체지향 프로그래밍 챕터에서 배우겠지만 지금까지 알고 있는 지식 선에서 설명하자면 참조 변수는 일반적인 변수처럼 데이터를 저장하는 공간이라기 보다는 데이터 저장 공간을 가르키는 주소를 저장해 놓은 공간을 참조 변수라고 한다.

  • 배열을 복사하는 두번째 방법은 System.arraycopy 메소드를 이용하는 방법이다.

        int[] num = {1,2,3,4,5,6,7,8,9};
        int[] newNum = new int[num.length * 2];
        System.arraycopy(num,0,newNum,0,num.length);
        System.out.println(Arrays.toString(newNum));
  • arraycopy의 첫번째 인자는 복사를 할 배열이다. 두번째는 복사를 할 배열의 몇번째 element부터 복사할지를 정하는 것이다. 세번째는 어디에 복사할 것인지 새로운 배열을 적는 곳이다. 네번째는 새로운 배열의 몇번째 element부터 복사를 할 것인지 적는 곳이다. 다섯번째는 복사를 할 배열의 끝 범위를 적는 곳이다.

1.6 배열의 활용

배열의 활용은 예제를 통해 공부해 보겠다.

배열 내 최대값, 최소값 찾기

        int[] num = {1,2,3,4,5,6,7,8,9};
        int max = num[0], min = num[0];
        for (int i=0; i< num.length; i++) {
            if (max < num[i]) max = num[i];
            else if (min > num[i]) min = num[i];
        }
        System.out.printf("%d\t%d",max,min);
  • 기준을 num[0]으로 잡은 후, for문을 돌려서 각 element마다 num[0]보다 크면 max에 그 값을 저장하고, 작으면 min에 그 값을 저장하도록 했다.

배열 element 섞기(로또)

        int[] lotto = new int[45];
        for (int i=0; i<45; i++) {
            lotto[i] = i+1;
        }
        int tmp, ball;
        for (int i=0; i<6; i++) {
            ball = (int) (Math.random() * 45); 
            // 0~45 중 랜덤으로 저장
            // lotto 배열의 element를 무작위로 뽑을 용도로 만듦
            tmp = lotto[i];
            lotto[i] = lotto[ball];
            lotto[ball] = tmp;
            System.out.println(lotto[i]);
        }
  • 로또 번호 1~45까지의 수를 배열로 만든다. for 문으로 ball 변수에 0~44 중 무작위의 수를 저장한다. ball은 lotto 배열의 인덱스로 활용되어서 lotto 배열의 수를 무작위로 뽑는 역할을 할 것이다. 그 다음 lotto의 배열 0번째부터 5번째까지 총 6개의 element를 다른 element로 랜덤으로 바꾼다. 그 후에 0~5번째의 lotto 배열 element를 출력한다.

배열 오름차순으로 정렬

public class Sample {
    public static void main(String[] args) {
        int[] arr = new int[10];
        int tmp;
        for (int i=0; i< arr.length; i++) {
            System.out.print(arr[i] = (int) (Math.random() * 10));
        }
        System.out.println();

        for (int i=0; i<arr.length-1; i++) {
            boolean changed = false;
            for (int j=0; j<arr.length-1-i; j++) {
                if (arr[j] > arr[j+1])  {
                    tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                    changed = true;
                }
            }
            if (!changed) break;
            for (int k=0; k<arr.length; k++) {
                System.out.print(arr[k]);
            }
            System.out.println();
        }
    }
}
  • 이 예제는 문제 자체를 이해하는 것만 해도 오래 걸렸고, 코드도 복잡해서 꽤나 고생을 했다. 일단 문제를 먼저 파악하자면 랜덤으로 10자리 배열을 받은 후, 오름차순으로 정리를 하는데 for 문이 한바퀴 돌때마다 배열을 출력하도록 하는 문제이다. 이 문제에서는 for 문 안에 두개의 for문이 들어가서 더 복잡했다.
  • 먼저 인접해 있는 두 수를 비교하여 앞의 수가 뒤의 수보다 크면 두 수를 바꾸도록 하기위해 for문의 범위를 배열길이-1까지로 지정했다. 그 다음 안쪽 for문을 또 작성하는데 안쪽 for문의 범위를 정하는게 좀 힘들었다. 안쪽 for문이 한바퀴씩 돌때마다 맨 뒷자리 수는 가장 큰 수가 될 것이다. 왜냐하면 인접한 수를 비교하여 큰수를 뒤로 보냈기 때문이다. 그러므로 맨 뒤의 수는 비교할 필요가 없다. 그래서 for문을 한바퀴씩 돌때마다 마지막수는 제외하기 위해 바깥 for 문의 변수인 i를 뺐다. 여기선 boolean 자료형도 사용했는데 그 이유는 만약 for 문이 다 돌기 전에 오름차순 정렬을 완료했다면 굳이 똑같은 배열을 출력할 필요가 없기 때문에 if문을 이용해서 배열의 변화가 없다면 break로 for문을 탈출하도록 했다. 그리고 안쪽의 또다른 for문을 만들어서 배열을 출력하도록 했다.

배열의 중복된 숫자 세기

public class Sample {
    public static void main(String[] args) {
        int[] arr = new int[10];
        int[] count = new int[10];
        for (int i=0; i<arr.length; i++) {
            arr[i] = (int) (Math.random() * 10);
            System.out.print(arr[i]);
            count[arr[i]]++;
        }
        System.out.println();
        for (int i=0; i<count.length; i++)
            System.out.printf("%d 갯수 : %d\n",i,count[i]);
    }
}
  • 0~9까지의 수를 랜덤으로 뽑아 크기가 10인 배열에 저장한다.
    그다음 크기가 10인 또 다른 배열을 생성해서 for 문으로 기존배열의 element를 새로운 배열의 인덱스에 넣어서 인덱스를 1씩 키운다. (int 배열은 선언 시 0으로 초기화되어 있다.)

3.0 다차원 배열

지금까지 배웠던 배열은 1차원 배열로 int[], String[] 등이였다. 다차원 배열을 공부하면서 2차원 이상의 배열을 만들 수 있다는 사실을 알게 되었다. 2차원 배열은 행렬과 유사한 성질을 가지고 있다. 다차원 배열 역시 예제를 통해 공부해 보겠다.

학생 점수표 작성하기

public class Sample {
    public static void main(String[] args) {
        int[][] score = {
                {100,100,100},
                {20,20,20},
                {30,30,30},
                {40,40,40},
                {50,50,50}
        };
        int korSum = 0, engSum = 0, matSum = 0;
        System.out.println("번호\t\t국어\t\t영어\t\t수학\t\t총합\t\t평균");
        System.out.println("============================================");
        for (int i=0; i<score.length; i++) {
            int sum = 0;
            float avg = 0;
            korSum += score[i][0];
            engSum += score[i][1];
            matSum += score[i][2];
            System.out.printf("%d\t",i+1);
            for (int j=0; j<score[i].length; j++) {
                sum += score[i][j];
                avg = (float) sum / score[i].length;
                System.out.printf("\t%3d\t", score[i][j]);
            }
            System.out.printf("\t%3d\t\t%3.1f\n",sum,avg);
        }
        System.out.println("============================================");
        System.out.printf("총점 :\t%3d\t\t%3d\t\t%3d",korSum,engSum,matSum );
    }
}
번호		국어		영어		수학		총합		평균
============================================
1		100		100		100		300		100.0
2		 20		 20		 20		 60		20.0
3		 30		 30		 30		 90		30.0
4		 40		 40		 40		120		40.0
5		 50		 50		 50		150		50.0
============================================
총점 :	240		240		240
  • 이 예제는 코드 자체를 이해하는데 어렵다기 보다는 코드의 순서를 잘 생각하고 작성해야 한다. sum, avg같은 경우 각 학생의 총합과 평균을 저장할 변수이므로 어디에 변수를 선언하는지에 따라 원하는 결과가 나오지 않을 수 있다. 이 두 변수는 행과 관련된 변수이므로 행이 끝날 때마다 0으로 다시 초기화 되어야 다른 학생의 총합과 평균을 다시 구할 수가 있다. 그러므로 바깥 for문(행 범위의 반복문)에 변수를 선언해야 한다.

지뢰 찾기 게임

public class Sample {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        final int SIZE = 10;
        char[][] board = new char[SIZE][SIZE];
        char[][] mineBoard = {
           //    1  2  3  4  5  6  7  8  9
                {0, 0, 0, 0, 0, 1, 0, 0, 0},//1
                {1, 0, 0, 0, 0, 0, 0, 0, 0},//2
                {0, 0, 0, 0, 0, 1, 0, 0, 0},//3
                {0, 1, 0, 0, 0, 0, 0, 0, 0},//4
                {0, 0, 1, 0, 0, 0, 0, 0, 0},//5
                {0, 0, 0, 0, 0, 0, 0, 0, 1},//5
                {0, 0, 0, 0, 0, 0, 0, 1, 0},//7
                {0, 0, 0, 0, 0, 0, 0, 1, 0},//8
                {0, 0, 0, 0, 0, 0, 1, 0, 0},//9
        };
        int x = 0, y = 0;
        for (int i = 1; i < SIZE; i++) {
            board[0][i] = board[i][0] = (char) (i + '0');
        }

        while (true) {
            System.out.print("좌표 입력(종료 : 00) > ");
            String input = scanner.nextLine();
            if (input.length() == 2) {
                x = input.charAt(0) - '0';
                y = input.charAt(1) - '0';
                if (x == 0 && y == 0) {
                    System.out.println("게임 종료");
                    break;
                }
            }
            if (input.length() != 2 || x < 0 || x >= SIZE || y < 0 || y >= SIZE) {
                System.out.println("잘못 입력");
                continue;
            }
            board[x][y] = mineBoard[x - 1][y - 1] == 1 ? 'O' : 'X';
            for (int i = 0; i < SIZE; i++) {
                for (int j = 0; j < SIZE; j++) {
                    System.out.printf("%c\t", board[i][j]);
                }
                System.out.println();
            }
        }
    }
}
  • 미리 크기가 9,9인 char 자료형의 배열 mineBoard을 만들어 놓은 후, 임의로 0과 1을 채워 넣는다. 1을 지뢰라고 가정한다. 그리고 크기가 10,10인 새로운 배열 board을 하나 더 만들고, 데이터를 넣지 않고 선언만 해놓는다. 이 배열은 게임판이 될 것이고, mineBoard와 비교하여 지뢰를 찾으면 'O'를 못찾으면 'X'를 출력할 것이다. Board의 크기가 10,10으로 더 큰 이유는 1행과 1열에 숫자를 넣어서 보기 편하게 하기 위함이다.

  • board에 숫자를 넣기 위해서 1행 1열에 for문으로 숫자를 넣은 다음 '0'을 더해서 int를 char로 자료형 변환을 한다.

  • while문을 작성한 후, Scanner로 좌표를 입력받도록 한다. 좌표는 String으로 입력받아서, 각자리 수를 charAt으로 나눌 수 있게끔 한다. 좌표는 두자리수이여야만 하므로 두자리수가 아니거나 음수거나 board 사이즈를 벗어나면 continue로 다시 입력받게끔 한다.

  • if문으로 좌표가 두자리수를 입력받았는지 조건문을 작성하고 참이면 좌표를 charAt으로 나누어 x,y 변수에 각각 저장한다. 저장할 때 자료형 변환을 해주어야 한다. String으로 입력받았으므로 int로 바꿔야 한다. x,y 변수는 각각 x,y좌표가 될 것이기 때문이다. 변환을 위해 각각 '0'을 빼준다. char 자료형 '0'을 빼주는 이유는 char자료형은 유니코드로 바뀌어 저장되기 때문이다.

  • 그 다음 board와 mineBoard를 비교하여 1을 찾았으면 'O', 0을 찾았으면 'X'를 반환하기 위해 조건연산자를 이용한다. board는 mineBoard보다 크기가 각각 1씩 크므로 mineBoard의 좌표 x,y에 1씩 빼준다.

조건식 ? 식1 : 식2;
  • board를 출력한다. board의 자료형은 char이기 때문에 위의 코드처럼 굳이 이중 for문을 사용하지 않아도 된다. char의 2차원 배열의 각 요소는 1차원 배열이기 때문이다. 예를 들어 아래 test를 출력하면 마치 String 자료형을 출력하는 것처럼 출력이 된다.
char[] test = {'A','B','C'};
System.out.println(test); // ABC 출력

빙고 게임

import java.util.Scanner;

public class Sample {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        final int SIZE = 5;
        int x = 0, y =0, tmp = 0, input = 0;
        int[][] bingo = new int[SIZE][SIZE];
        for (int i=0; i<SIZE; i++) {
            for (int j=0; j<SIZE; j++) {
                bingo[i][j] = i * SIZE + j + 1;
            }
        }

        for (int i=0; i<SIZE; i++) {
            for (int j=0; j<SIZE; j++) {
                x = (int) (Math.random() * SIZE);
                y = (int) (Math.random() * SIZE);
                tmp = bingo[i][j];
                bingo[i][j] = bingo[x][y];
                bingo[x][y] = tmp;
            }
        }

        do {
            System.out.print("숫자 입력(1~25) > ");
            input = scanner.nextInt();
            OUTER:
            for (int i=0; i<SIZE; i++) {
                for (int j=0; j<SIZE; j++) {
                    if (input == bingo[i][j]) {
                        bingo[i][j] = 0;
                    } else if (input == 0) break OUTER;
                    System.out.printf("%3d\t",bingo[i][j]);
                }
                System.out.println();
            }
        } while (input != 0);
    }
}
  • 먼저 5X5 빙고를 하기 위해 크기가 5,5인 int타입의 배열을 선언한다. 그 후에 for문으로 차례대로 1~25를 채워넣는다. 수를 채워 넣는 것부터 고비의 시작이였는데 수열의 규칙성을 빨리 찾아내는 것이 관건인 것 같다.

  • 그 후에 또 다른 for문을 만들어서 x,y 변수에 각각 0~4 중 하나의 수를 무작위로 저장한다. 이 변수들은 역시 좌표값이 될 것이다. 무작위로 저장된 x,y좌표를 이용해서 순서대로 작성된 빙고판을 무작위로 섞는다.

  • 그 후에 do-while 문을 이용한다. Scanner로 1~25 중 수를 입력받도록 한 후, 입력 받은 수에 해당하는 배열 위치에 0을 저장한다.

  • 코드를 짜는 방법에 대해서 정확히 배우지 않아서 잘은 모르겠지만 어떤 변수가 필요할지 먼저 생각해봐야 더 좋은 학습 방법이 될까? 아직까지는 일단 코드를 작성하면서 필요한 변수를 그때 그때 선언하는 방법으로 코드를 작성하고 있다. 이런 세세한 공부 방법론마저 잘 모르겠어서 함께 공부할 사람이나 선생님이 필요할 것 같다..

0개의 댓글