[Java_BenchMark]로또 boolean배열 vs set 의 활용

Wang_Seok_Hyeon·2023년 8월 27일
0
post-thumbnail

Intro

매번 나는 글을 쓸 때 Intro->Outro형태로 글을 쓴다. 글의 간단한 소개. 글의 끝맺음을 목표로 한다.(중간에 말이 길어지는 걸 고쳐 보자!)

Random 라이브러리

자바의 java.util.Random이라는 라이브러리가 있다.
이를 활용하는 대표적인 예제? 활용 과제처럼 빈번하게 나오는 게 로또다.

이 때, 얼마나 잘 포맷을 잘 구현하느냐와 더 높은 효율을 내느냐 등 여러가지 차원의 문제가 나온다.

이에 관해 Math.random()을 베이스로 해서, 별도의 Random을 import하지 않고 사용하는 방법을 활용하는 TOY_PROJECT를 진행했다.

TOY_PROJECT 간단 소개

목적성이 있다기 보다, 잘 사용해 오지 않은 라이브러리를 연습하고자 하는 목적이었는데 재밌게 잘 했던 것 같다. 그러면서 오히려 여러 가지 구현을 했고 이 중에

단순하게 6자리의 조합을 만들게 되면 겹치는 경우가 발생하기 때문에 이를 제거해 줄 필요성이 있었다.

이를 위해 boolean[] 을 이용한 방법과
Set을 이용한 중복 제거 방식을 고민했고,
고민 하지 말고 둘 다 구현하는 형태를 취했다.

간단하게 코드에 있는 여타 요소를 소개하면 아래와 같다.
아래는 각각의 코드로 메서드 부분을 먼저 소개한다.
이때, 주석 처리 된 값을 통해 실질적인 값들을 가지고 올 수 있으며,
기본적으로 이 코드들의 성능 비교를 위해 System.currentTimeMillis();를 활용한다.

   private static long boolean배열로또(){
        long startTime = System.currentTimeMillis();
        //StringBuilder sb = new StringBuilder();
        for(int i = 0; i < 10; i++){
                int cnt = 0;
                boolean [] Lotto = new boolean[46];
                while(cnt < 6){
                    int pickNum = (int)(Math.random() * 45) + 1;
                    if(Lotto[pickNum]) continue;
                    cnt++;
                    //sb.append(pickNum).append(" ");
                    Lotto[pickNum] = true;
                }
                //sb.append("\n");
            }
            //System.out.println(sb);
            long endTime = System.currentTimeMillis();
             return endTime-startTime;
        }
 private static long set활용로또(){
        long startTime = System.currentTimeMillis();
        
        //StringBuilder sb = new StringBuilder();
        for(int i = 0; i < 10; i++){
            Set<Integer> set = new HashSet<>();
            int randNum = -1;
            for(int j = 0; j < 6; j++){
                do{
                    randNum = (int)(Math.random()*45 + 1);
                }while(set.contains(randNum));
                set.add(randNum);
                //sb.append(randNum).append(" ");
            }
            //sb.append("\n");
        }
        //System.out.println(sb);
    long endTime = System.currentTimeMillis();
        return endTime-startTime;
 
    }

벤치마크에 대해서 알아보자.

이제 코드를 보았고, 이 코드를 통해 벤치마크를 진행할 예정이다.

다만, 벤치마크이며 이 각각의 성능을 알아보기 위함이고 얼마나 차이가 나는지 알기 위해서는 반복을 통해 해당 성능을 확인해 볼 수 있으리라 생각했다.

그래서 Iterator를 두었고 이를 통해, 값의 반복을 진행했고, long으로 값의 범위를 잡아, 최대 9,223,372,036,854,775,807까지의 수를 지원하도록 했다.
뿐만 아니라, 음수가 들어오면 이를 처리한다. 이때 오버플로우가 되면 결국 음수가 되기 때문에 이에 대한 경우는 무시했으며

프로그램의 안내를 9,223,372,036,854,775,807 이상은 지원하지 않습니다.를 만들어 두었다.

또한,음수 이외의 기타 문자열이 들어오는 경우도 예외로 처리되게 했기 때문에 예외처리도 어느정도 해둔 코드를 완성했다.

테스트 코드에 대한 부분이 아직 부족한 요소라고 생각되지만 애초에 MVC, SOLID 등을 적용한 게 아니라 말 그대로 재미로 만든 것이기 때문에 가볍게 봐 주시면 됩니다.

자바만을 활용해 front, ui 등을 구현하기 위한 방법이 있었고 이를 통해 파일 저장, 저장 경로를 저장해 이전 저장 경로에 저장하기,
이벤트 발생을 엔터를 통해 주기, close해야지 프로그램이 종료되는 등의 이벤트를 만들었다.

아래는 전체 코드다.(해당 코드에 여전히 주석으로 남아있는 코드가 있는데 이는 레거시다. 로또가 잘 나오는지 확인하기 위한 목적으로 남겨두기는 했지만 활용 여부는 개발자 또는 이용자에게 맡기겠다.)

import javax.swing.*;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

public class boolean배열_Set벤치마크 {
    // 이전에 저장한 경로를 저장하는 정적 변수
    private static String previousPath = "";

    public static void main(String[] args) {
        JFrame frame = new JFrame("로또 boolean 배열 Set 활용 성능 비교");
        frame.setSize(400, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel();
        frame.add(panel);
        placeComponents(panel);

        frame.setVisible(true);
    }

    private static void placeComponents(JPanel panel) {
        panel.setLayout(null);

        JLabel userLabel = new JLabel("9,223,372,036,854,775,807 이상은 지원하지 않습니다.");
        userLabel.setBounds(10, 10, 300, 25);
        panel.add(userLabel);

        JTextField userText = new JTextField(20);
        userText.setBounds(10, 30, 160, 25);
        panel.add(userText);

        JButton runButton = new JButton("실행(Enter)");
        runButton.setBounds(190, 30, 120, 25);
        panel.add(runButton);

        JTextArea resultArea = new JTextArea();
        resultArea.setBounds(10, 120, 360, 100);
        panel.add(resultArea);

        // 실행 로직을 별도의 메서드로 분리
        ActionListener runAction = e -> {
            try {
                long iterator = Long.parseLong(userText.getText());
                // 양수 확인
                if (iterator <= 0) {
                    throw new IllegalArgumentException("양수를 입력해주세요.");
                }

            long startTime = System.currentTimeMillis();
            long booleanTotal = 0;
            long setTotal = 0;

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < iterator; i++) {
                booleanTotal += boolean배열로또();
                setTotal += set활용로또();
            }
            long endTime = System.currentTimeMillis();
            String result = sb.append(iterator).append(" 실행 횟수에 대한 결과입니다.\n")
                .append("boolean배열을 활용한 결과값의 평균은 ").append(booleanTotal).append("ms 입니다.\n")
                .append("Set을 활용한 결과값의 평균은 ").append(setTotal).append("ms 입니다.\n")
                .append("전체 프로그램의 수행 시간은 ").append(endTime - startTime).append("ms 입니다.\n").toString();
            resultArea.setText(result);

            // 파일 저장 대화 상자
            JFileChooser fileChooser = new JFileChooser();
            if (!previousPath.isEmpty()) {
                fileChooser.setCurrentDirectory(new File(previousPath));
            }
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            String defaultFileName = "로또 성능 비교 결과_" + sdf.format(new Date()) + ".txt";
            fileChooser.setSelectedFile(new File(defaultFileName));
            int userSelection = fileChooser.showSaveDialog(null);

            if (userSelection == JFileChooser.APPROVE_OPTION) {
                File fileToSave = fileChooser.getSelectedFile();
                previousPath = fileToSave.getParent();
                try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileToSave))) {
                    writer.write(result);
                } catch (IOException ioException) {
                    ioException.printStackTrace();
                }
            }
            } catch (NumberFormatException ex) {
                resultArea.setText("부적합한 요청입니다. 양수를 입력해주세요.");
            } catch (IllegalArgumentException ex) {
                resultArea.setText(ex.getMessage());
            }
        };

        // 버튼에 액션 리스너 추가
        runButton.addActionListener(runAction);

        // 텍스트 필드에 액션 리스너 추가 (엔터 키를 눌렀을 때 동작)
        userText.addActionListener(runAction);
    }

        private static long boolean배열로또(){
        long startTime = System.currentTimeMillis();
        //StringBuilder sb = new StringBuilder();
        for(int i = 0; i < 10; i++){
                int cnt = 0;
                boolean [] Lotto = new boolean[46];
                while(cnt < 6){
                    int pickNum = (int)(Math.random() * 45) + 1;
                    if(Lotto[pickNum]) continue;
                    cnt++;
                    //sb.append(pickNum).append(" ");
                    Lotto[pickNum] = true;
                }
                //sb.append("\n");
            }
            //System.out.println(sb);
            long endTime = System.currentTimeMillis();
             return endTime-startTime;
        }
    
    private static long set활용로또(){
        long startTime = System.currentTimeMillis();
        
        //StringBuilder sb = new StringBuilder();
        for(int i = 0; i < 10; i++){
            Set<Integer> set = new HashSet<>();
            int randNum = -1;
            for(int j = 0; j < 6; j++){
                do{
                    randNum = (int)(Math.random()*45 + 1);
                }while(set.contains(randNum));
                set.add(randNum);
                //sb.append(randNum).append(" ");
            }
            //sb.append("\n");
        }
        //System.out.println(sb);
    long endTime = System.currentTimeMillis();
        return endTime-startTime;
 
    }

}

Outro

위의 코드는 github에서 확인이 가능하다.
out 폴더에 .jar과 .bat 파일을 만들어 두었으며 .bat을 클릭하면 실행이 된다.

java11에서 만들어 졌으며 cmd를 이용해
java -jar <fileName\>.jar을 통해 실행 할 수도 있다.

이번 토이 프로젝트에서 재밌었던 게 정말 많은데 File 라이브러리 사용과 swing 라이브러리의 사용, awt의 사용, 그리고 jar 파일 만들기 과정과 MANIFEST.MF 만들기. 그리고 .bat 파일 만들기 까지
정말 작게 시작했고 실제로도 작았지만 그 과정과 들어가 있는 의존성.
이를 통해 산출하는 결과물 등은 결코 가볍지 않다는 느낌이었다.

다만, 이 가볍지 않은 일을 재미로 할 수 있었다는 점이 많이 뿌듯했고, 이런게 내가 원했던 개발이지 않을까 생각하고 더 좋은 더 많은 것들을 만들고 이를 통해 더 좋은 개발자가 되기 위해 노력해야겠다라는 생각을 하게 되었던 것 같다.

다음 번엔 정렬을 비교하는 프로그램을 만들어 볼까 하는 생각이 있으며 이에 대해서 각각의 모듈로 만드는 SpringBoot를 이용하면 각각의 코드의 효율성.

레거시인 대표예제들을 가지고 이야기 해 볼 수 있지 않을까 싶다.

-끝-

profile
하루 하루 즐겁게

0개의 댓글