원래 deck은 String 타입의 배열이었다.
(deck은 블랙잭 카드게임에 쓰이는 카드를 저장하는 공간으로, 총 208개의 String(카드종류+숫자/문자)을 저장)
public class Deck{
String[] deck = new String[kind.length * num.length * round];
private Deck(){
int deckNum = 0;
for(int i=0; i<4; i++){
for(int j=0; j<kind.length; j++){
for(int k=0; k<num.length; k++){
deck[deckNum++] = kind[j]+num[k];
}
}
}
deckNum = 0;
}
...
public String hit(){
int ranNum = 0;
String returnCard = "";
do{
ranNum = (int)(Math.random() * deck.length);
returnCard = deck[ranNum];
if(!"".equals(returnCard)){
deck[ranNum] = "";
break;
}
} while(true);
return returnCard;
}
}
처음에는 게임도중 카드를 저장하는 공간의 길이를 수정할 필요가 없어보였다. 또 블랙잭 게임을 하는데 52장의 카드 4팩을 사용해서 카드의 중복이 생기기 때문에 Set은 당연히 사용못하고, List도 (블랙잭 게임에서 hit을 할 때) 길이를 수정하면 요소들을 옮겨야하는데 자원이 많이 사용될거라 생각해 deck을 String 배열로 정했다.
하지만 여기에 과연 do-while문이 사용되는게 맞을까 싶어 다시 자바의 정석 collection 파트를 뒤적이던 중 갑자기 유투브에서 찾아본 블랙잭 게임 영상이 떠올랐다.
이미지 출처 : https://www.gamblingsites.org/blog/can-you-count-cards-with-continuous-shuffling-machine/
딜러는 게임이 시작되면 새 카드 덱을 꺼내어 그대로 위의 사진과 같은 기계에 집어 넣은 뒤, 한 장씩 꺼낸다. 카드는 이미 이 기계 안에서 섞여 새롭게 정렬되었기 때문에 한장씩 차례로 꺼내도 문제가 없다.
그래서 이 기계의 역할을 shuffle()과 hit()이 그대로 나눠서 하면 되겠다는 생각에 다시 아래처럼 리팩토링했다.
public class Deck{
List<String> deck = new ArrayList<>(kind.length * num.length * round);
private Deck(){
for(int i=0; i<4; i++){
for(int j=0; j<kind.length; j++){
for(int k=0; k<num.length; k++){
deck.add(kind[j]+num[k]);
}
}
}
}
...
public void shuffle(){
int ranNum = 0;
String temp = "";
for(int i=0; i<deck.size(); i++){
ranNum = (int)(Math.random() * deck.size());
temp = deck.get(ranNum);
deck2.set(ranNum, deck.get(i));
deck2.set(i, temp);
}
}
public String hit(){
String returnCard = deck2.get(deck2.size()-1);
deck2.remove(deck2.size()-1);
return returnCard;
}
}
두 코드 간의 성능을 확인하기 위해 각각 test를 10번 돌려 보았다.
전후가 각각 0.0481s, 0.0497s로 평균 시간이 비슷한 것을 확인할 수 있었다.
더 돌려보니 어느게 더 빠른지 확인하는게 의미가 없어보였으나, 리팩토링 이후가 더 빨리 돌아갈 것으로 생각했던 나에게는 예상 외의 결과였다.
어디서 시간이 걸렸는지 확인을 해보니,
deck.add(kind[j]+num[k]);
shuffle()이나 hit()을 실행하는데는 시간이 거의 안걸렸고 (0ms에 수렴..)
Deck을 생성하며 arrayList의 add()를 사용할 때 시간이 대부분 걸렸다.
ArrayList는 capacity이상의 데이터가 저장되려면 capacity를 늘리는게 아니라, 더 큰 arrayList를 생성해서 거기에 데이터를 복붙하는 형식으로 크기를 늘린다. 그걸 감안하고 ArrayList를 생성할 때 생성자에 initialCapacity를 딱 데이터 갯수대로 넣었지만
List<String> deck = new ArrayList<>(kind.length * num.length * round);
그래도 데이터를 저장하는데 시간이 오래 걸린 것이다. 이는 배열에 값을 저장할 때도 마찬가지였다.
작은 어플리케이션이라 성능에는 큰 차이가 없었지만 조금 더 논리적으로 코드를 짤 수 있었다. 실제 사물을 모방하는 것이 코딩에 도움이 된다.