ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [부트캠프] NBCStudentManager 3일차
    부트캠프/프로젝트 2024. 8. 16. 19:59

    프로젝트 명 NBCStudentManager 

    과제와 공부를 병행하는 과정에서 2일차 회고 작성일로 부터 텀이 무척이나 길어졌다.

    아마 이 글이 해당 프로젝트에 대한 마지막 회고가 될 것 같다.

     

    3일차에 내가 맡은 역할

    기능을 구현하다 보면 테스트를 하기 위해 가상의 데이터를 활용한 개발이 필요할 때가 있다.그것은 우리 또한 마찬가지 였다.

     

    저장 기능이 있어 상대적으로 부담은 덜 했지만, 그럼에도 일일히 학생 정보를 작성하고 추가해가는 것은 너무나도 번거로운 일이었다.

     

    그래서 나는 학생 정보와 점수 정보에 대한 더미데이터를 만들어주는 그런 Class를 생성하기로 했다.

     

    기능 구현

    우선, 이 프로젝트에서 사용하는 데이터는 Student와 Score이렇게 2가지 정보를 사용한다.

    그래서 각각 더미데이터를 생성하는 StudentFactory, ScoreFactory Class를 생성했다.

     

    StudentFactory

    StudentFactory는 성, 이름, 상태, 필수 과목, 선택 과목에서 각각 랜덤한 정보를 뽑아조합하여 StudentClass를 반환해주는 Class이다.

     

    이름, 상태는 기존에 final로 설정해둔 FAMILY_NAMES( 성 ), FIRST_NAMES( 이름 ), STATUS( 상태 )에서 각각 무작위 값을 뽑아와 조합하는 방식으로 구현했다.

     

    이름, 상태 추출하는 함수

    더보기
    final String[] FAMILY_NAMES = { "김", "이", "박", "최", "정", "강", "조", "윤", "장", "임", "한", "오"};
    final String[] FIRST_NAMES = {"민준", "서준", "도윤", "시우","지호","지후","도현","건우","우진","선우","연우","서진","현준",
                "시윤","윤우","지우","유찬","수호","승민","진우","민성","지원","시현","한결","지안","시원","윤호","은호","서우","우주",
                "민규","민찬","하율","준","지율","승준","현서","민호","로","윤재","이현","지성","하민","민혁","성준","태양","도하",
                "예찬","다온","이든","주안"
    };
    final String[] STATUS = {"Red", "Yello", "Green"};
        
    // 무작위 과목들을 추출하는 함수
    public String createStudentName(Random rand) {
        return FAMILY_NAMES[rand.nextInt(FAMILY_NAMES.length)] + FIRST_NAMES[rand.nextInt(FIRST_NAMES.length)];
    }
        
    // 랜덤한 스테이트 생성하는 함수
    public String createStatus(Random rand) {
        return STATUS[rand.nextInt(STATUS.length)];
    }

     

    필수 과목과 선택 과목에 대해서는 고민을 많이 했다.이름, 상태에는 별다른 조건이 붙지 않았지만, 필수 과목과 선택 과목에 대해서는 조건이 붙기 때문이다.

     

    조건

    • 필수 과목 : 최소 3개 이상
    • 선택 과목 : 최소 2개 이상

     

    우선 듣는 과목수를 무작위로 지정했다. 필수 과목은 3개 이상이라는 점을 이용하여,

    • 필수 과목 : 3 ~ 필수 과목 수 중 무작위 1개 선정
    • 선택 과목 : 2 ~ 선택 과목 수 중 무작위 1개 선정

    위의 방식으로 과목 수를 정했다.

    그 다음, 전체 필수 or 선택 과목에서 다음과 같은 무작위로 추출하는 방식을 사용했다.

    • 과목 중에 무작위로 1개를 고른다.
    • 해당 과목이 이미 존재할 경우, 다시 고른다.

    하지만, 위와 같이 구현을 할 경우 운이 정말 없다면 무한루프에 빠질 가능성이 존재했고,

    이미 존재하는지 확인하는 과정이 필요했다.

     

    그래서 나는 다음과 같은 다른 방식을 채용했다.

    • 0 ~ n - 1의 숫자 중 무작위로 1개를 고른다.
    • 해당 Index의 과목과 n - 1번째 과목의 위치를 바꾼다.
    • --n을 해준다.

    아마 글로만 봤을 때에는 아리송 할 수도 있기에 표로 나타내 보겠다.

    •  Rand( 0 ~ N(5) ) => 2
    0 1 2 3 4

     

    • 2와 4를 Swap
    0 1 4 3 2

     

    •  Rand( 0 ~ N(4) ) => 0
    0 1 4 3 2

     

    • 0와 3를 Swap
    3 1 4 0 2

     

    이렇게 진행할 경우, 중복 걱정을 하지 않아도 되기에 시간 복잡도가 일정하다는 장점이 존재한다.

     

    createSubjects 메서드

    더보기
        // 무작위 과목들을 추출하는 함수
        public List<Long> createSubjects(Random rand) {
            int mandatorySubjectCount = rand.nextInt(MIN_MANDATORY_COUNT, mantatorySubjectIds.length + 1);
            int choiceSubjectCount = rand.nextInt(MIN_CHOICE_COUNT, choiceSubjectIds.length + 1);
    
            List<Long> mandatorySubjects = Utility.permutation(mantatorySubjectIds, rand, mandatorySubjectCount);
            List<Long> choiceSubjects = Utility.permutation(choiceSubjectIds, rand, choiceSubjectCount);
    
            return Stream.concat(mandatorySubjects.stream(), choiceSubjects.stream())
                    .collect(Collectors.toList());
        }

    permutation 메서드

    더보기
    // elements에서 count만큼의 랜덤한 원소를 뽑아주는 함수
    public static<T> List<T> permutation(T[] elements, Random rand, int count) {
        List<T> result = new ArrayList<>(elements.length);
        int maxRange = 0;
    
        for(int i = 0; i < count; ++i) {
            // 원소 최대 범위 ( maxRange 이후 = 이미 뽑힌 것들 )
            maxRange = elements.length - result.size();
            int index = rand.nextInt(maxRange);
    
            result.add(elements[index]);
            // 뽑은 것은 맨 뒤로 보낸다.
            swap(elements, index, elements.length - result.size());
        }
    
        return result;
    }

     

    StudentFactory 전체 코드

    더보기
    package dummy;
    
    import model.Student;
    
    import java.util.*;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    public class StudentFactory {
        final String[] FAMILY_NAMES = { "김", "이", "박", "최", "정", "강", "조", "윤", "장", "임", "한", "오"};
        final String[] FIRST_NAMES = {"민준", "서준", "도윤", "시우","지호","지후","도현","건우","우진","선우","연우","서진","현준",
                "시윤","윤우","지우","유찬","수호","승민","진우","민성","지원","시현","한결","지안","시원","윤호","은호","서우","우주",
                "민규","민찬","하율","준","지율","승준","현서","민호","로","윤재","이현","지성","하민","민혁","성준","태양","도하",
                "예찬","다온","이든","주안"
        };
        final String[] STATUS = {"Red", "Yello", "Green"};
    
        final int MIN_MANDATORY_COUNT = 3;
        final int MIN_CHOICE_COUNT = 2;
    
        private Long[] mantatorySubjectIds;
        private Long[] choiceSubjectIds;
        private Long studentId;
    
        public StudentFactory() {
            studentId = 0L;
        }
    
        public void initialize() {
            mantatorySubjectIds = new Long[DummyDataFactory.MANDATORY_TYPE_SUBJECT_COUNT];
            choiceSubjectIds = new Long[DummyDataFactory.CHOICE_TYPE_SUBJECT_COUNT];
    
            for(long i = 0; i < DummyDataFactory.MANDATORY_TYPE_SUBJECT_COUNT; ++i) {
                mantatorySubjectIds[(int)i] = i;
            }
    
            for(long i = 0; i < DummyDataFactory.CHOICE_TYPE_SUBJECT_COUNT; ++i) {
                choiceSubjectIds[(int)i] = DummyDataFactory.MANDATORY_TYPE_SUBJECT_COUNT + i;
            }
        }
    
        // 무작위 학생 이름 생성해주는 함수
        public String createStudentName(Random rand) {
            return FAMILY_NAMES[rand.nextInt(FAMILY_NAMES.length)] + FIRST_NAMES[rand.nextInt(FIRST_NAMES.length)];
        }
    
        // 무작위 과목들을 추출하는 함수
        public List<Long> createSubjects(Random rand) {
            int mandatorySubjectCount = rand.nextInt(MIN_MANDATORY_COUNT, mantatorySubjectIds.length + 1);
            int choiceSubjectCount = rand.nextInt(MIN_CHOICE_COUNT, choiceSubjectIds.length + 1);
    
            List<Long> mandatorySubjects = Utility.permutation(mantatorySubjectIds, rand, mandatorySubjectCount);
            List<Long> choiceSubjects = Utility.permutation(choiceSubjectIds, rand, choiceSubjectCount);
    
            return Stream.concat(mandatorySubjects.stream(), choiceSubjects.stream())
                    .collect(Collectors.toList());
        }
    
        // 랜덤한 스테이트 생성하는 함수
        public String createStatus(Random rand) {
            return STATUS[rand.nextInt(STATUS.length)];
        }
    
        // 랜덤한 학생 생성하는 함수
        public Student createRandomStudent(Random rand) {
            return new Student(studentId++, createStudentName(rand), createSubjects(rand), createStatus(rand));
        }
    }

     

    ScoreFactory

    ScoreFactory는 1 ~ 10사이 개수의 무작위 점수, 등급을 갖는 Score객체를 생성하는 Class다.

     

    해당 Factory에 대해서는 Random(0, 10)을 10번 굴려 시험 회차를 구하고,

    Random(0, 100)을 통해 해당 시험 회차의 점수를 구하는 방식으로 간단하게 구현했다.

     

    이번에는 이미 점수를 추가 했는지에 대한 여부를 체크하지 않았는데,

    그 이유는 이미 존재하는지에 대한 여부를 체크하는 것보다 덮어 씌우는 것이 

    더 효율적이라고 판단 했기 때문이다.

     

    ScoreFactory.java

    더보기
    package dummy;
    
    import model.Score;
    import java.util.Random;
    
    
    import static dummy.DummyDataFactory.MANDATORY_TYPE_SUBJECT_COUNT;
    
    public class ScoreFactory {
        final int SCORE_RANGE = 100;
        final int ROUND_RANGE = 10;
    
        public void setRandomScore(Random rand, Score score) {
            for(int i = 0; i < 10; ++i) {
                score.setScores(rand.nextInt(ROUND_RANGE), rand.nextInt(SCORE_RANGE));
            }
        }
    
        // 랜덤한 학생 생성하는 함수
        public Score createRandomScore(Random rand, long studentId, long subjectId) {
            String subjectType = subjectId < MANDATORY_TYPE_SUBJECT_COUNT ? "SUBJECT_TYPE_MANDATORY" : "SUBJECT_TYPE_CHOICE";
            Score result = new Score(studentId, subjectId, subjectType);
    
            setRandomScore(rand, result);
    
            return result;
        }
    }

     

    문제점

    학생, 과목, 점수를 연결하는 과정에서 생긴 문제

    학생 List에 존재하지 않는 학생이 시험을 볼 가능성은 없어야 한다.

    그렇기에 학생 List에 존재하는 학생으로만 점수 Score를 만들어야 하고,

    이미 해당 과목으로 시험을 봤는지에 대해서도 체크를 해야 한다.

     

    그래서 나는 학생의 ID와 과목 ID를 조합하여 String형태로 변환한 뒤,

    Set에 저장하여 중복을 체크하게 만들었다.

     

    위에서 사용했던 대로 Swap하는 방식도 고민 했지만,

    한 한생이 다수의 과목을 수강할 수 있는 상황에서는 사용이 어려울 것 같아 

    Set을 사용하기로 마음 먹었다. 

     

    최종적으로 플로우는 다음과 같았다.

    • "{studentID} {subjectID}" 형태의 문자열을 생성한다.
      • ex) 0 1
    • 해당 문자가 createdCombination이라는 문자열 Set에 들어있는지 체크한다.
    • 들어있지 않다면 추가하고, 랜덤 Score값을 ScoreFactory로 부터 받아온다.

    DummyDataFactory Class

     

    더보기
    package dummy;
    
    import model.Score;
    import model.Student;
    
    import java.util.*;
    
    public class DummyDataFactory {
        Random rand;
        StudentFactory studentFactory;
        ScoreFactory scoreFactory;
    
        static int MANDATORY_TYPE_SUBJECT_COUNT = 5;
        static int CHOICE_TYPE_SUBJECT_COUNT = 4;
    
        public DummyDataFactory() {
            rand = new Random();
            studentFactory = new StudentFactory();
            scoreFactory = new ScoreFactory();
    
            studentFactory.initialize();
        }
    
        public Student getRandomStudent(List<Student> students) {
            int index = rand.nextInt(students.size());
    
            return students.get(index);
        }
    
        public long getRandomSubject(List<Long> subjects) {
            int index = rand.nextInt(subjects.size());
    
            return subjects.get(index);
        }
    
        // 학생 더미데이터를 추가해주는 함수
        public List<Student> createStudentDummyData(int count) {
            List<Student> students = new ArrayList<Student>();
    
            for (int i = 0; i < count; i++) {
                students.add(studentFactory.createRandomStudent(rand));
            }
    
            return students;
        }
    
    
        // Score 더미데이터 생성하는 함수
        public List<Score> createScoreDummyData(int count, List<Student> students) {
            List<Score> scores = new ArrayList<Score>();
            // 학생 과목 아이디 조합으로 이미 생성된 조합인지 확인하기 위한 용도
            Set<String> createdCombination = new HashSet<String>();
    
            while(scores.size() < count) {
                Student student = getRandomStudent(students);
                long subjectId = getRandomSubject(student.getSubjects());
                String combination = String.format("%d %d", student.getStudentId(), subjectId);
    
                // 이미 생성된 경우
                if(createdCombination.contains(combination)) {
                   continue;
                }
    
                createdCombination.add(combination);
                scores.add(scoreFactory.createRandomScore(rand, student.getStudentId(), subjectId));
            }
    
            return scores;
        }
    }

     

    결과물

    [ 무작위 정보로 생성된 더미 데이터, (왼) 학생 정보, (오) 10회 점수, 학생 ID, 과목 ID ]

     

Designed by Tistory.