[LIKELION] 221031

고관운·2022년 10월 31일

회고

😄 느낀점

  • 오랜만에 parsing하는 코드를 구현하는 실습을 했는데 그 당시는 완벽하게 이해했다고 생각했지만 기억에 남는 것이 별로 없었다. 시간이 지나도 계속적인 반복이 필요하다는 것을 다시 한번 느꼈다.

😁 목표

  • 프로그래머스 알고리즘

알고리즘

모의고사(프로그래머스)

문제

👀 문제 확인하기 👀

방법

  1. 수포자의 패턴을 배열로 미리 선언
  2. 각 수포자가 몇 문제 맞췄는지 알 수 있도록 크기가 3이고 0으로 초기화된 배열 선언
  3. 각 수포자들의 정답 개수를 구할 때, 나머지 연산자를 활용하여 각 수포자의 패턴을 반복하도록 구현
  4. 수포자 중 가장 높은 점수 구하기
  5. list로 가장 높은 수포자를 모두 구하기
  6. 배열로 바꾸어 리턴
import java.util.ArrayList;

class Solution {
    public int[] solution(int[] answers) {
        int[] person1 = {1, 2, 3, 4, 5};
        int[] person2 = {2, 1, 2, 3, 2, 4, 2, 5};
        int[] person3 = {3, 3, 1, 1, 2, 2, 4, 4, 5, 5};

        int[] correctValue = {0, 0, 0};

        // %연산자를 활용하여 각 수포자별 정답 개수 갱신
        for (int i = 0; i < answers.length; i++) {
            if(answers[i] == person1[i % person1.length]){
                correctValue[0]++;
            }
            if(answers[i] == person2[i % person2.length]){
                correctValue[1]++;
            }
            if(answers[i] == person3[i % person3.length]){
                correctValue[2]++;
            }
        }

        // 수포자 중 가장 큰 정답 개수 구하기
        int maxValue = -1;
        for (int i = 0; i < 3; i++) {
            if(maxValue < correctValue[i])  maxValue = correctValue[i];
        }

        // list로 점수가 가장 높은 수포자 넣기
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            if(maxValue == correctValue[i]){
                list.add(i+1);
            }
        }

        // 이후, 배열로 바꾸어 리턴
        int[] answer = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            answer[i] = list.get(i);
        }

        return answer;
    }
}

Spring Boot

@SpringBootTest

@SpringBootTest

SpringBoot가 스캔을 해서 등록한 Bean을 Test에서 쓸 수 있게 해줌

아래 코드와 같은 기능
@ExtendsWith
@ConfiguraionContext

@Autowired

  • ApplicationContext = Spring 에 등록된 빈을 '이름'에 맞게 DI를 해줌

  • 즉, = new ReadlineContext() ~~~ 에서 new를 안하게 해줌
    (Singletone : 객체 한번 생성 -> 계속 객체를 생성하지 않도록)
    (여러명(ex 10,000명) 1만번의 GC가 돌 수 있기 때문에 Singletone으로 띄움)

  • Autowired는 주로 Test에서 쓰는 추세
    (서비스 코드는 final과 Constructor를 씁니다. 이렇게 해도 Spring이 DI를 해줌)

Hospital Project

실습 과정

  1. 전국 병원 정보가 들어갈 Table 설계
  2. 설계한대로 Create Table
  3. Table에 넣기 위한 Domain Class 만들기
  4. Parsing 코드 구현 및 테스트

MySQL 데이터베이스 Column 타입

  • tinyint : 작은 int 범위(int에 비해 공간을 덜 차지함)
  • datetime : where 조건에 날짜 from to 가능
  • float : 소수점(int에 넣으면 소수점이 잘리기 때문에 int에 비해 공간을 더 차지함)

Column 타입을 세분화하는 이유
DB에 저장공간을 줄이고, 검색 속도를 최적화하기 위함

실습

  1. 전국 병원 정보가 들어갈 Table 설계

🟢 datetime으로 설정한 이유
👉 사용빈도가 datetime > date임
이번 경우에는 인허가일자에 date를 해도 괜찮음
(이것만 date 형식이므로)
단, date, datetime 형식이 섞여있으면 datetime으로 통일하여 사용

  1. 설계한대로 Create Table
CREATE TABLE `likelion-db`.`nationwide_hospitals` (
  `id` INT NOT NULL,
  `open_service_name` VARCHAR(10) NULL COMMENT '개방서비스명',
  `open_local_government_code` INT NOT NULL COMMENT '개방자치단체코드',
  `management_number` VARCHAR(40) NULL COMMENT '관리번호',
  `license_date` DATETIME NULL COMMENT '인허가일자',
  `business_status` TINYINT(2) NULL COMMENT '영업상태명',
  `business_status_code` TINYINT(2) NULL COMMENT '상세영업상태코드',
  `phone` VARCHAR(20) NULL COMMENT '소재지전화',
  `full_address` VARCHAR(200) NULL COMMENT '소재지전체주소',
  `road_name_address` VARCHAR(200) NULL COMMENT '도로명전체주소',
  `hospital_name` VARCHAR(20) NULL COMMENT '사업장명',
  `business_type_name` VARCHAR(10) NULL COMMENT '업태구분명',
  `healthcare_provider_count` TINYINT(3) NULL COMMENT '의료인수',
  `patient_room_count` TINYINT(3) NULL COMMENT '입원실수',
  `total_number_of_beds` TINYINT(3) NULL COMMENT '병상수',
  `total_area_size` FLOAT NULL COMMENT '총면적',
  PRIMARY KEY (`id`),
  UNIQUE INDEX `management_number_UNIQUE` (`management_number` ASC) VISIBLE);
  1. Table에 넣기 위한 Domain Class만들기
    🔸 Spring 에노테이션
  • @AllArgsConstructor : 모든 변수를 가진 생성자
  • @Getter : Getter
  • @Setter : Setter
  • @NoArgsConstructor : 기본 생성자
import java.time.LocalDateTime;

@AllArgsConstructor // 모든 변수를 가진 생성자
@Getter             // Getter
@Setter             // Setter
@NoArgsConstructor  // 기본생성자
public class Hospital {
    private int id;
    private String OpenServiceName;
    private int openLocalGovernmentCode;
    private String managementNumber;
    private LocalDateTime licenseDate;
    private int businessStatus;
    private int businessStatusCode;
    private String phone;
    private String fullAddress;
    private String roadNameAddress;
    private String hospitalName;
    private String businessTypeName;
    private int healthcareProviderCount;
    private int patientRoomCount;
    private int totalNumberOfBeds;
    private float totalAreaSize;
}
  1. Parsing 코드 구현 및 테스트
public interface Parser<T> {
    T parse(String str);
}
import com.springboot.springbootcoreguide.domain.Hospital;

import java.time.LocalDateTime;

public class HospitalParser implements Parser<Hospital> {
    @Override
    public Hospital parse(String str) {
        String[] row = str.split("\",\"");

        Hospital hospital = new Hospital();
        hospital.setId(Integer.parseInt(row[0].replace("\"", "")));
        hospital.setOpenServiceName(row[1]);
        hospital.setOpenLocalGovernmentCode(Integer.parseInt(row[3]));
        hospital.setManagementNumber(row[4]);

        int year = Integer.parseInt(row[5].substring(0, 4));
        int month = Integer.parseInt(row[5].substring(4, 6));
        int day = Integer.parseInt(row[5].substring(6));
        hospital.setLicenseDate(LocalDateTime.of(year, month, day, 0, 0, 0));

        hospital.setBusinessStatus(Integer.parseInt(row[7]));
        hospital.setBusinessStatusCode(Integer.parseInt(row[9]));
        hospital.setPhone(row[15]);
        hospital.setFullAddress(row[18]);
        hospital.setRoadNameAddress(row[19]);
        hospital.setHospitalName(row[21]);
        hospital.setBusinessTypeName(row[25]);
        hospital.setHealthcareProviderCount(Integer.parseInt(row[29]));
        hospital.setPatientRoomCount(Integer.parseInt(row[30]));
        hospital.setTotalNumberOfBeds(Integer.parseInt(row[31]));
        hospital.setTotalAreaSize(Float.parseFloat(row[32].replace("\"", "")));

        return hospital;
    }
}
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class ReadLineContext<T> {

    private Parser<T> parser;

    public ReadLineContext(Parser<T> parser) {
        this.parser = parser;
    }

    public List<T> readByLine(String filename) throws IOException {
        // 삽
        List<T> result = new ArrayList<>();
        BufferedReader reader = new BufferedReader(
                new FileReader(filename)
        );
        String str;
        while ((str = reader.readLine()) != null) {
            try {
                result.add(parser.parse(str));
            } catch (Exception e) {
                System.out.printf("파싱 중 문제가 생겨 이 라인은 넘어갑니다. 파일내용:%s\n", str);
            }
        }
        reader.close();
        return result;
    }
}
import com.springboot.springbootcoreguide.domain.Hospital;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ParserFactory {

    @Bean
    public ReadLineContext<Hospital> hospitalReadLineContext() {
        return new ReadLineContext<Hospital>(new HospitalParser());
    }
}
import com.springboot.springbootcoreguide.domain.Hospital;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class HospitalParserTest {

    String line1 = "\"1\",\"의원\",\"01_01_02_P\",\"3620000\",\"PHMA119993620020041100004\",\"19990612\",\"\",\"01\",\"영업/정상\",\"13\",\"영업중\",\"\",\"\",\"\",\"\",\"062-515-2875\",\"\",\"500881\",\"광주광역시 북구 풍향동 565번지 4호 3층\",\"광주광역시 북구 동문대로 24, 3층 (풍향동)\",\"61205\",\"효치과의원\",\"20211115113642\",\"U\",\"2021-11-17 02:40:00.0\",\"치과의원\",\"192630.735112\",\"185314.617632\",\"치과의원\",\"1\",\"0\",\"0\",\"52.29\",\"401\",\"치과\",\"\",\"\",\"\",\"0\",\"0\",\"\",\"\",\"0\",\"\",";

    @Autowired
    ReadLineContext<Hospital> hospitalReadLineContext;

    @Test
    @DisplayName("10만 건 이상 데이터가 파싱되는지")
    void OneHundreadThousandRows() throws IOException {
        String filename = "D:\\고관운 자료\\멋쟁이사자처럼\\수업 자료\\fulldata_01_01_02_P_의원_utf8.csv";
        List<Hospital> hospitalList = hospitalReadLineContext.readByLine(filename);
        assertTrue(hospitalList.size() > 100000);

        for (int i = 0; i < 10; i++) {
            System.out.println(hospitalList.get(i).getHospitalName());
        }
    }

    @Test
    @DisplayName("csv 1줄을 Hospital로 잘 만드는지 Test")
    void convertToHospital() {
        HospitalParser hp = new HospitalParser();
        Hospital hospital = hp.parse(line1);

        assertEquals(1, hospital.getId());
        assertEquals("의원", hospital.getOpenServiceName());
        assertEquals(3620000, hospital.getOpenLocalGovernmentCode());
        assertEquals("PHMA119993620020041100004", hospital.getManagementNumber());
        assertEquals(LocalDateTime.of(1999, 6, 12, 0, 0, 0), hospital.getLicenseDate());
        assertEquals(1, hospital.getBusinessStatus());
        assertEquals(13, hospital.getBusinessStatusCode());
        assertEquals("062-515-2875", hospital.getPhone());
        assertEquals("광주광역시 북구 풍향동 565번지 4호 3층", hospital.getFullAddress());
        assertEquals("광주광역시 북구 동문대로 24, 3층 (풍향동)", hospital.getRoadNameAddress());
        assertEquals("효치과의원", hospital.getHospitalName());
        assertEquals("치과의원", hospital.getBusinessTypeName());
        assertEquals(1, hospital.getHealthcareProviderCount());
        assertEquals(0, hospital.getPatientRoomCount());
        assertEquals(0, hospital.getTotalNumberOfBeds());
        assertEquals(52.29f, hospital.getTotalAreaSize());
    }
}

🔴 4번 과정을 겪으면서 어려웠던 점

  1. 주소에 ,가 있었기 때문에 ,로 split을 하지 못함
    👉 ","으로 split한 후, 0과 마지막 인덱스의 값에 "제거
  2. LocalDateTime 형식으로 넣어줘야함
    👉 subString으로 연월일을 나눈 후 넣어주고, 시간은 모두 0으로 넣어줌
  3. ReadLineContext을 활용하여 Parsing하는 과정에서 데이터가 올바르지 않아 에러 발생
    👉 try catch문으로 에러 메세지 출력
    👉 월이 00인 경우도 있어서 어떻게 처리할 것인지 기준을 정해야함
  4. Factory 부분
    👉 이 부분은 조립하는 과정으로 이해하는 것에 많은 시간을 쏟아야함
  5. 데이터 읽을 때, 인코딩 에러
    👉 나의 경우 메모장으로 UTF-8로 인코딩 변경해줌

🔴 데이터 오류 처리 방법

  1. 데이터 파싱 중 에러나면 그 데이터를 제거
  2. null인 경우 0 또는 default value를 넣는 방법 - 기준을 정해야 함

0개의 댓글