자바 이터레이터 응용 - 경품 추첨
요즘 어디가나 경품이 핫하죠? 이터레이터 패턴을 응용하여 경품 추첨 프로그램을 만들어 보겠습니다.
이 프로그램은 경품추첨 명단과 경품의 리스트를 받아 추첨하는 과정을 수행하는 프로그램입니다. 추첨 기능은 두가지여서 경품에 따라 차례로 명단에서 이름을 뽑는 방식과 명단에 있는 사람 중에서 랜덤한 순서로 한명씩 경품을 추첨하는 형태입니다.
먼저 전체적인 프로그램의 실행의 전체구조를 살펴보겠습니다. 명단을 가질 mList와 입력을 받을 스캐너를 만들고 명단을 읽어들인 후 전체 출력하고 나서 추첨부를 호출합니다.
public class EventMgr {
Scanner scan = new Scanner(System.in);
ArrayList<String> mList = new ArrayList<>();
void run() {
readAll();
printAll();
draw();
}
// 상품 목록
String items[] = {"아이패드프로", "아이팟", "웹하드10G1Y", "보조밧데리", "다음기회에"};
int ea[] = {1, 3, 5, 10, -1}; // 상품 개수
void draw() {
int menu = 0;
System.out.print("추첨 방식 (1) 이름뽑기 (2) 선물뽑기 => ");
menu = scan.nextInt();
if (menu == 1) drawNames();
else if (menu == 2) drawItems();
}
추첨부 draw() 메소드는 이름뽑기와 선물뽑기로 나누어지는데요, 여기서 선물의 리스트와 선물의 개수를 가진 배열을 먼저 선언했습니다. 이것을 입력 받도록 고치면 더 좋겠지요?
그럼 먼저 이름을 뽑는 코드를 살펴보겠습니다.
// 뽑을 사람의 수만큼 반복하면서 랜덤하게 상품 추첨
void drawNames() {
Iterator<String> it = new NameSelector(mList);
String winner = null;
scan.nextLine();
for (int i = items.length - 2; i >= 0; i--) {
for (int j = 0; j < ea[i]; j++) {
System.out.println("=>" + (i+1) + "등");
winner = it.next();
System.out.println("축하합니다 " + winner + " : " + items[i]);
scan.nextLine();
}
}
System.out.println("감사합니다. 안녕히 가세요.");
}
이름을 랜덤하게 뽑아주는 이터레이터를 NameSelector라는 이름의 클래스로 구현했구요, 이것으로 선물을 마지막 등수부터 1등까지 차례로 개수만큼 이름을 뽑아 결과를 보여줍니다. 경품은 보통 마지막 등수부터 뽑잖아요? 그리고 각 등수에 따라 상품의 개수가 있으니까 그만큼 반복해야 할 것입니다. 엔터 칠 때마다 다음 사람을 뽑도록 하기 위해 scan.nextLine()을 넣어주었습니다.
다음 위너를 뽑는 부분에서 it.next()를 써서 추첨하는데요, 이때 추첨대상자가 상품보다는 많다는 가정을 하기 때문에 hasNext를 검사하지 않고 그냥 for 루프를 돌리고 있습니다.
그럼 진짜 비밀이 담긴 이터레이터 코드를 한번 살펴볼까요? 이 클래스는 이터러블과 이터레이터를 둘다 구현하고 있는데요, 이터러블은 두번째 추첨 방식인 경품 뽑기에서 쓰이는데, 여기서는 일단 간단하게 코드만 보고 넘어가면 되겠습니다. 이터러블을 구현하기 위한 iterator() 메소드에서 return this; 하는 것을 볼 수 있습니다. 자기 자신이 이터레이터이자 이터러블이 되는 구조이지요.
class NameSelector implements Iterator<String>, Iterable<String>
{
ArrayList<String> candidates;
NameSelector(ArrayList<String> candidates) {
this.candidates = new ArrayList<>(candidates);
Collections.shuffle(this.candidates);
}
@Override
public boolean hasNext() {
return !candidates.isEmpty();
}
@Override
public String next() {
return candidates.remove(0);
}
@Override
public Iterator<String> iterator() {
return this;
}
}
그럼 이터레이터의 구현부를 살펴볼까요? 생성자는 랜덤하게 추첨할 이름 리스트를 받아 먼저 shuffle해서 섞어줍니다. 그리고 next할 때마다 한 개씩 이름을 돌려주는데, 리스트에서 제거하여 다시 추첨되지 않도록 하고 있습니다. hasNext()는 이 리스트에 뭐가 더 남아있으면 true이고 비어있으면 false가 됩니다.
이 이터레이터를 이용하여 랜덤한 순서로 이름을 하나씩 뽑을 수 있겠지요?
그럼 두번째 춫첨 방식인 경품 뽑기를 살펴보겠습니다. 이번에는 후보자들 중에 랜덤하게 한명씩 나와서 경품을 추첨하는 방식으로 하고 싶은데요, 코드는 다음과 같습니다. 여기서 NameSelector가 이터러블로 쓰인 것을 볼 수 있습니다. 이터러블이니까 for-each에서 사용될 수 있는 거지요. 그리고 경품 추첨은 이터레이터인 Drawer 클래스를 이용하고 있습니다.
여기서 주의할 점은 사람의 랜덤한 추첨 순서와 상관없이 상품이 나올 확률은 같아야 되니까 미리 전체가 몇명인지 알 수 있어야 한다는 점입니다. 그래서 경품 추첨 이터레이터를 만들 때 사람의 수 mList.size()를 매개변수로 주고 있습니다.
// 랜덤한 순서로 사람을 추첨하고 그 사람이 경품을 다시 추첨함
void drawItems() {
Iterable<String> nameIt = new NameSelector(mList);
Iterator<String> it = new Drawer(mList.size(), items, ea);
String win;
scan.nextLine();
for (String name : nameIt) {
System.out.println("=>" + name);
scan.nextLine();
win = it.next();
if (win.equals("items[items.length-1]"))
System.out.println(win);
System.out.println("축하합니다 " + name + " : " + win);
}
System.out.println("감사합니다. 안녕히 가세요.");
}
이름을 랜덤한 순서로 뽑는 것은 for 루프의 NameSelector가 제공하는 이터레이터로 하고 경품을 추첨하는 것은 Drawer 클래스의 next() 기능을 이용하고 있습니다. 여기서 중요한 것은 경품이 개수만큼만 추첨되어야 한다는 점인데 이것을 보장하기 위해 Drawer 클래스에서는 뽑힌 경품의 개수를 감소시켜야 합니다. 그리고 마지막 경품으로 "다음기회에"를 넣어서 추첨되지 않은 사람은 "다음기회에"가 나와야 되고 그것은 개수를 감소시킬 필요없이 얼마든지 나와도 되게 따로 처리하였습니다.
class Drawer implements Iterator<String>
{
static Random rand = new Random();
int count;
String items[];
int ea[];
// 생성자에서 전체 사람의 수, 경품 종류와 개수를 설정합니다.
Drawer(int n, String[] items, int[] ea) {
count = n;
this.items = items.clone();
this.ea = ea.clone();
}
@Override
public boolean hasNext() {
return true; // 다음기회에는 계속 나올 수 있으므로 항상 true
}
@Override
public String next() {
int num = rand.nextInt(count);
int cc = 0;
for (int i = 0; i < ea.length; i++) {
if (ea[i] == -1) // 다음기회에는 계속 나올 수 있음
return items[i];
cc += ea[i]; // 등수에 따라 그 안에 들어가는 숫자인지 검사
if (num < ea[i]+cc) {
ea[i]--; // 뽑힌 상품은 개수 차감
return items[i];
}
}
return null;
}
}