서비스가 커지고 문제가 복잡해질수록, 이를 처리하기 위한 소프트웨어의 아키텍쳐적인 부분이 중요해진다.

 

내가 진행했던 토이프로젝트의 서버는 크게 세 가지 일을 했다.

 

-새로운 데이터를 처리

-서비스로직을 처리(시간표그려주기 등)

-기존의 데이터를 이용 (db 활용)

 

Spring 에서는 해당 부분들이 각각의 레이어로 나누어져있다.

 

-Presentation layer : 사용자와 상호작용하는 처리 계층. CLI, HTTP요청, HTML처리 등을 담당 (Model, View, Controller..)

   ==> 특정 url에 요청을 보내면 해당 url에 묶인 메서드가 호출되었던 부분과 비슷.

 Spring에서 @Controller로 표현

 

-Domain(business / service) layer : 서비스나 시스템의 핵심 로직. 유효성 검사, 계산 등 어플리케이션의 도메인과 관련된 작업들을 담당함. 프로그램이 복잡해지면, 비지니스 로직을 수행하기 위한 별도의 계층이 필요하다. 그게 Domain layer

 Spring에서 @Service로 표현

 

-Data Access(persistence) Layer (DAO 계층) : Database / Message Queue / 외부 API와의 통신 등을 처리한다.

DB 혹은 데이터 소스가 서버 외부에 별개로 존재하는 경우가 매우 많기 때문에, 데이터 소스와의 소통계층이 필요함.

Spring에서 @Repository로 표현

 

레스토랑에 비유하면

@Controller : 웨이터

@Service : 쉐프

@Repository : 주방보조 (재료가져다줌)

 

<Database>

DBMS : Database Management System

RDBMS : Relational Database Magagement System

==RDBMS==============

-테이블이라는 최소  단위로 구성되고, 테이블은 열과 행으로 이루어져있다.

-MySQL, PostgreSQL, Oracle 등

======================

 

H2 : In-memory DB의 대표주자. 인메모리DB란 서버가 작동하는 동안에만 내용을 저장하고, 서버를 닫으면 데이터가 모두 삭제  ==> 연습용으로 좋다.

 

<SQL> : Structured Query Language  :  RDBMS에서 사용하는 언어

 

=====DDL=====  : Data Definition Language   : 테이블이나 관계의 구조를 생성하는데 사용

CREATE DATABASE 데이터베이스이름;

CREATE TABLE 테이블이름

(

필드이름1 필드타입1,

필드이름2 필드타입2,

...

)

ALTER TABLE 테이블이름 ADD 필드이름 필드타입;

ALTER TABLE 테이블이름 DROP 필드이름;

ALTER TABLE 테이블이름 MODIFY COLUMN 필드이름 필드타입;

 

DROP DATABASE 데이터베이스이름;

DROP TABLE 테이블이름;

TRUNCATE DATABASE 데이터베이스이름;

TRUNCATE TABLE 테이블이름;

 

=====DCL=====  : Data Control Language   :  데이터의 사용 권한을 관리하는데 사용

GRANT SELECT , INSERT          : 권한 부여

ON mp

TO scott WITH GRANT OPTION;

 

REVOKE SELECT, INSERT         :  권한 회수

ON emp

FROM scott

[CASCASE CONSTRAINTS];

 

=====DML=====  : Data Manipulation Language   :  테이블에서 데이터를 검색 / 삽입 / 수정 / 삭제

INSERT INTO 테이블이름 (필드이름1,필드이름2,필드이름3...) VALUES(데이터값1, 데이터2, 데이터3....)

INSERT INTO 테이블이름 VALUES (데이터1,데이터2,데이터3....)

 

SELECT 필드이름 FROM 테이블이름 WHERE[조건];

 

UPDATE 테이블이름 SET 필드이름1 = 데이터값1, 필드이름2 = 데이터값2...... , WHERE 필드이름 = 데이터값;

 

DELETE FROM 테이블이름 WHERE 필드이름 = 데이터값;

 

 

<SQL Cheatsheet>

=====CREATE 제약조건들=====

AUTO_INCREMENT  :  컬럼의 값이 중복되지 않게 1씩 자동으로 증가하게 해줘 고유번호를 생성

NOT NULL : 해당필드는 NULL값을 저장할 수 없게됨

UNIQUE : 해당필드는 서로 다른값을 가져야만 함

PRIMARY KEY : 해당필드가 NOT NULL과 UNIQUE 특징을 모두 가지게 됨

     ==> 테이블에 유일하게 존재하는 값의 조합을 설정해서 중복된 데이터가 테이블에 삽입되는 것을 방지하는 제약조건

     ==> 데이터의 무결성을 위해서 사용, 데이터를 빠르게 검색할 수 있게 됨

FOREIGN KEY : 두개의 테이블을 연결하는 다리 역할을 하는 KEY 

     ==> FOREIGN KEY (필드이름) REFERENCES 테이블이름(필드이름)

     ==> FOREIGN KEY 역시 데이터의 무결성을 보장. FK를 적용하려는 컬럼이 참조하는 테이블의 컬럼은 PK, UNIQUE

 

 

어플리케이션이 데이터베이스를 직접 다룰 때의 문제점

- 훨씬 번거롭다 : 테이블 만들고, 쿼리 작성하고, 쿼리를 jdbc api통해 실행, 결과 객체도 직접 만들어줘야함

- SQL의존적이다: SQL에 컬럼을 추가하면, 관련된 객체, 쿼리문 등등 모든 부분을 수정해야함 => 비지니스로직보다 SQL↑

- 패러다임 불일치

https://knoc-story.tistory.com/m/90

==> ORM, JPA 기술의 등장

ORM : object relation Mapping

 

<JPA>  : Java Persistence API

=>자바 ORM 기술에 대한 표준 명세

1. 쿼리를 자동으로 만들어줌

2. 어플리케이션 계층에서 sql 의존성을 줄임 (번거로운 반복작업 ↓)

3. 패러다임의 불일치 해결

4. 방언을 지원하기 때문에, h2 DB, mySQL, oracle 등 SQL표준을 준수한 DB무엇을 붙여도 같은 코드로 쓸 수 있다.

 

@Entity     annotation을 통해 해당 클래스가 DB의 테이블 역할을 한다는 것을 알려준다.

@OneToMany  // @ManyToOne // @OneToOne // @ManyToMany   : Entity들의 연관관계 형성

 

eg) Food 엔티티의 food // Orders 엔티티의 food_id가 매핑되어야 한다고 할 때,

Food 클래스 안에서:

@OneToMany(mappedBy = "food", fetch = FetchType.EAGER)

private List<Orders> orders = new ArrayList<>();

 

Orders 클래스 안에서:

@ManyToOne

@JoinColumn(name = "food")

private Food food;                      ==> 생성자까지 추가해줘야함

 

repository 패키지에 각 entity별로 FoodRepository와 같이 생성

public interface FoodRepository extends JpaRepository<Food, Long>{}

foodRepository.saveAll(List<Food>)와 같이 사용 가능

메서드들 : save, saveAll, findAll, findById 등등

웬만한 기능들은 JpaRepository에 구현 되어있기 때문에, Repository 구현체에서 override 해주면 된다.

만들 수 있는 것들이 추천됨

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods  에서 query method 들을 확인가능

 

<추가 공부>

-영속성 컨텍스트, 1차 캐시

 

 

 

https://www.lesstif.com/java/intellij-file-console-encoding-121012310.html

'공부 > 잡다' 카테고리의 다른 글

QueryDSL 사용법  (0) 2023.05.03
Springboot 저장소  (0) 2023.04.16
웹 동작 간략개괄  (0) 2023.04.14
IntelliJ 디버거 활용 - Exception 조건설정  (0) 2023.04.10
Twitter recommendation system code revealed  (0) 2023.04.06

부트캠프 시작 전 삼성 SCSA 전형에 지원했고, 부트캠프 1주차에 경험삼아 네이버에 지원했다.

4/15 9:00 ~ 10:30 삼성 GSAT  예비소집 일정이 생겼고, 네이버 코테는 같은 날 10:00에 시작이었다.

삼성 예비소집 일정을 변경하려면 4/4까지 신청을 했어야하는데, 네이버 지원 자체를 그 이후에 했기 때문에 두 가지를

다 참여할 수는 없었다.

코테와 GSAT 둘다 준비가 덜 된 상태에서 응시해봐야 붙을 가능성이 희박한 것은 매한가지였지만, 개발자의 진로를 선택

한 이상 코딩테스트에 응시해보는 것이 더 좋은 경험이라고 생각해서 네이버 코딩테스트를 응시하기로 하였다.

 

요새 배우고 있는 자바로 응시했는데, 결과는 0솔로 처참했다. 4문제 다 지문이 길었고, 로직 자체는 어렵다고 생각하지 않았으나, 개인적으로 프로그래머스 문제를 풀 때와는 느낌이 달랐다. 구현 문제와 탐색 문제가 나왔는데, 탐색을 하는 로직은 이해했으나 계산 과정의 분기에서 중간 결과를 어떻게 저장해야하고, 어떤 자료형을 써야하는지 명확하게 생각이 나지 않아서 어려웠다. 프로그래머스의 어려운 문제들을 풀 때 느낀 감정 그대로였다. BFS,DFS의 개념을 알아도, 실제로 문제를 풀면서 방법을 손에 익히지 않으면 어렵다고 생각했다. 어쩌면 자바가 손에 익었는지와는 별개로 Python으로 문제를 푸는 것이 더 간결하고, 로직에 집중할 수 있을 것 같다고 느꼈다.

 

다소 우울했고, GSAT 볼걸 그랬나.... 라는 생각이 들지 않았다면 거짓말이지만 코딩테스트를 통해 나중을 위해서라도 알고리즘 공부를 꾸준히 해야겠다는 동기부여가 되었다. 개발공부 한지 한 달도 안돼서 좋은 결과를 바라는 것이 오히려 과욕일것이다. 아쉽지만 다음을 기약하고, 내가 어려워하는 문제들 위주로 꾸준히 연습해서 구현력을 키워야겠다. 결론적으로 후회는 없다.

'취준' 카테고리의 다른 글

안랩 AI 서비스 개발 코테합격  (0) 2023.08.22
현대오토에버 탈락  (0) 2023.08.22

IP주소 : 네트워크 공간 상의 주소같은거

 

브라우저 : 웹페이지, 이미지, 비디오 등을 송수신하는 소프트웨어

                 ==> 컨텐츠를 송/수신하고, 표현하는 것

 

DNS : Domain Name Server

         ==> 192.168.0.123과 같은 IP주소가 아닌, hello.com과 같은 이름으로 서버에 접근 가능하게 하는 중개서버

 

HTTP(HyperText Transfer Protocol) 프로토콜 : 클라이언트와 서버 간 데이터를 주고받는 양식을 정의한 "통신 규약"

         ==> 웹 통신의 표준어라고 생각하면 된다. 

 

API(Application Programming Interface) : 다른 소프트웨어 시스템과 통신하기 위해 따라야 하는 규칙을 정의

 

REST(ful) API (Representational State Transfer) : API 작동 방식에 대한 조건을 부과하는 소프트웨어 아키텍쳐

         ==> REST 아키텍쳐를 따르는 API를 REST api // 이를 구현하는 웹서비스를 RESTful 웹 서비스

 

DB : 데이터를 효율적으로 사용(CRUD) 하기 위해서 db를 활용한다.

 

https://aws.amazon.com/ko/what-is/restful-api/

 

RESTful API란 무엇인가요? - RESTful API 설명 - AWS

Amazon API Gateway는 어떤 규모에서든 개발자가 API를 손쉽게 생성, 게시, 유지 관리, 모니터링 및 보안 유지할 수 있도록 하는 완전관리형 서비스입니다. API Gateway를 사용하면 실시간 양방향 통신 애

aws.amazon.com

 

 

 

<HTTP>

항상 Request, Response라는 개념이 존재함. 브라우저가 요청 보냄 -> 서버는 해당 데이터를 응답 -> 브라우저가 그려줌

Request : 브라우저가 요청한 데이터 //&nbsp;Response : 서버가 응답한 데이터
서버에서 브라우저로 반환해준 웹페이지를 그려주기 위한 데이터

Method : 호출 / 요청 방식

-GET : 리소스를 얻을 때

-POST : 웹서버에 데이터를 게시할 때

 

Header : 추가데이터 / 메타데이터

-브라우저가 어떤 페이지를 원하는지

-요청받은 페이지 / 데이터를 찾았는지

-어떤 형식으로 데이터를 보낼지

 

Payload : 실제 데이터

-서버가 응답을 보낼 때는 항상 Payload를 보낼 수 있다.

-클라이언트(브라우저)도 역시 보낼 수 있다. 일반적으로, GET method를 제외하고는 모두 보낼 수 있다.

 

 

 

 

 

'공부 > 잡다' 카테고리의 다른 글

Springboot 저장소  (0) 2023.04.16
IntelliJ 콘솔창 한글 깨질때  (0) 2023.04.15
IntelliJ 디버거 활용 - Exception 조건설정  (0) 2023.04.10
Twitter recommendation system code revealed  (0) 2023.04.06
Open-AI ChatGPT plugin  (0) 2023.04.06

https://school.programmers.co.kr/learn/courses/30/lessons/64065

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

열받게도 문제를 또 잘못 읽어버린 ㅡㅡ (정답 풀이 맨 아래)

 

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓삽질의 시작↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

<문제 이해>

주어지는 집합의 size가 순차적으로 1 -> 2 -> 3 ->4 .... 순이므로 (<== 삽질의 근원), n번째에 주어지는 집합에서 n-1번째 집합을 빼고 남는 원소가 n번째에 주어진 원소가 될 것이다. n 번째에 주어진 원소를 An이라고 하면, 결과로 반환해야하는 튜플은

(A1, A2, A3, A4.....)가 되어야 함. 즉, 주어진 Set들의 Set을 돌면서 이전 Set에 추가된 원소들을 찾으면 될 것.

 

<풀이 구상>

1. input[i]를 돌면서 input[i-1]의 원소들을 하나씩 지워나가고, 남는 원소를 FinalSet에 추가

    ==> 각 시행마다 input[i]의 길이 * input[i-1]의 길이를 탐색하므로 시간 오래걸릴 듯

 

2. input[i]와 input[i-1]를 Set으로 만들어주고, Set끼리의 연산

   ==> 파이썬처럼 Set - Set 연산이 가능한지 알아봐야함

   ==> int[]를 Set으로 만드는 것 자체가 오래걸리지 않을까 ?

   ==> 문제에서 중복이 없음을 보장하고 있는데 Set을 쓰는게 맞는가?

 

<구현가능성>

===============문제를 확인해보니, 주어지는 input이 배열이 아니라 String임============

★String 처리하여 각 input을 받아올 방법 필요

 

-파이썬처럼 Set - Set 연산이 가능한지 ?  ==> .removeAll(c) 메서드로 가능

AbstractSet의 removeAll 메서드

myself.removeAll(Collection c) ==> 내 자신의 원소가 collection c에 있으면 remove.

결국 iterator를 끝까지 도는 것은 매한가지다. 효율은 그닥일 듯. 이 함수를 쓰려면 input string s 를 거꾸로 읽는게좋겠다.

번외로, retainAll이라는 메서드도 있다.

 

==> 어차피 돌거면 그냥 앞에서부터 읽으면서, 그동안 나온 숫자를 모아놓은 SET을 유지   (=> 어차피 중복X 보장이고, 순서를 알아야하므로 List로 하기로 함)하고, i번째 집합의 원소마다 contains를 확인하여 없으면 answer에 붙이고 없으면 pass하도록 하는게 나을 것 같다고 생각함

 

-String을 읽으면서 {와 } 사이의 숫자를 읽는 방법이 필요함

buffer라는 개념이 생각남  ==> 추후 공부하여 정리하기

버퍼는 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 메모리의 영역

자세히는 모르지만, 이런 방법을 생각했다

1. {를 만나면 버퍼를 연다 

2. ,를 만나면 버퍼에 저장된 String을 숫자로 변환해 기존 Set에 있는지 검사

3. Set에 없으면 Set에 넣고, 진행 // Set에 있으면 버퍼를 닫고 다음 }까지 진행

 

이를 위해 buf라는 char[] 배열을 선언하였고, 이 배열의 내용물을 Int로 바꾸는 메서드와 buffer를 clear해주는 메서드를 작성하였다. ==> size를 저장하는 변수를 관리해야하는데, 어디 선언해야 예쁜지 몰라서 그냥 List<Character>로 진행

<구현>

 

<문제>

1. 내 Buffer를 String으로 바꾸는 방법에 대한 문제

buffer를 List<Character>로 구현했더니, buffer의 내용물을 String으로 바꿔주는 과정에서

Character List에 담긴 2가 String "2"로 변환되지 않고, "[2]"로 변환되는 문제가 있었다.

 buf의 타입을 바꿔줘야 할 것 같았지만, 일단 코드 작동을 확인하기 위해 정규표현식으로 [와 ]를 벗겨주었더니, 

첫번째 예시에 대해서 통과하여 일단 로직이 잘못되지는 않았다는 걸 알 수 있었다. 그러나,

한자리수를 초과하는 숫자에 대해서 "20"과 같은 String으로 나오는 것이 아니라 "2, 0"으로 나와 숫자로 변환이 안됐다.

buffer.toString()이   "[2, 0]"을 return 했다는 얘기였다.

String.valueOf(buf) 도 마찬가지였다.

String.valueOf() 메서드는 파라미터로 Object가 들어왔을 때, object가 null이 아니면 object의 toString() 함수를 호출한다고 되어있다. 따라서 String.valueOf(buf) ==  buf.toString() 메서드 (현재 buf는 List<Character>인 Object 니까)

List의 toString() 메서드를 보면,

List.toString()

사람이 이 Object에 뭐가 담겨있는지 한 눈에 볼 수 있도록 변환하도록 되어있다.

ArrayList의 element type은 Map이 될 수도 있고, Integer가 될 수도 있고, 어떤 객체든 올 수 있다.

만약 [1, 2, 3, 4, 5, 6]이 List로 담겨있을 때 내가 바랐던 것처럼 하나의 String에 이 요소들을 모두 모으게 되면,

"123456"이 되어 이 List가 한 자리 숫자들을 하나의 element로 담고있다는 정보가 없어질 것이다. 타입이 Map이거나 다른 사용자가 정의한 Class라면 더 어지러울 것이므로, toString()의 return값은 "[요소1, 요소2]" 이런식의 String으로 반환하여 로그를 찍어볼 때 한 눈에 볼 수 있게 해둔 것 같다.

★char[]같은 경우 각 element에 문자가 딱 하나씩 담긴게 보장이 되므로, "abcde"같은 string으로 반환해준다.

int, char, long, float, double, boolean, char[]에 대해서는 String 클래스 안에 정의되어있지만,

int[] 타입의 ints의 경우 Object로 간주해 int[]에 override 되어있는 toString()함수를 호출하게 하는 것을 보면, 숫자 하나, 문자하나 또는 char[]과 같이  헷갈릴만한 소지가 없는 경우에만 String.valueOf를 정의하고 있는 것을 알 수 있다.

 

<문제 해결>

방법1. buf를 List<Character>로 유지할 경우 : StringBuilder 클래스를 사용한다.

방법2. buf를 char[] 로 바꿔줄 경우 : toString()으로 변환 가능. 대신, List의 이점을 포기해햐함

                                                                                         ==>buffer size 저장, buffer clear 간편

 

조금 귀찮아도 방법 2로 하기로 했다.

 

buflen 추가하여 버퍼를 관리

 

<더 큰 문제>

또 문제를 똑바로 안읽었다. input = {{집합1}, {집합2}, {집합3}.... } 에서 input 역시 집합이므로 안에 원소가 길이가 바뀔 수가 있다는 것......... 나는 예시1만 보고 냅다 로직을 구상해버린 것이었다.😭😭

어쩐지 쉽더라 .... 정신적 충격이 심해 조금 쉬었다가 해야겠다..

 

============================================================================================

 

<새로운 풀이>

 

정신이 말짱해지고 오니 왜 저렇게 했는지 모르겠다. 난 정규표현식과 Comparator를 아는데 !!

 

1.input s 에서 처음 등장하는 {{와 마지막에 등장하는 }}를 지워준다.

2.split( " },{ ")로 각 내부집합을 받아온다

3. 2에서 받아온 내부집합은 String[] 인데, custom Comparator를 사용하여 길이순으로 정렬해준다.

4.내부집합의 원소들을 돌면서 Set에 있는지 확인하고, 없으면  tuple에 넣어준다

 

import java.util.*;
class Solution {
    public static int[] solution(String s) {
        List<Integer> tuple = new ArrayList<>();

        String regex = "^\\{\\{|}}$";  // s 시작의 {{, 끝의 }}를 지워줌
        String[] toarr = s.replaceAll(regex,"").split("},\\{");
        Arrays.sort(toarr, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length();
            }
        });

        for(String set:toarr){
            String[] nums = set.split(",");
            for(String num:nums){
                int intNum = Integer.parseInt(num);
                if (!tuple.contains(intNum)){
                    tuple.add(intNum);
                    break;
                }
            }
        }
        int[] answer = new int[tuple.size()];
        for(int i = 0; i< tuple.size(); i++){
            answer[i] = tuple.get(i);
        }

        return answer;
    }
}

 

처음 했던 방식보다 훨씬 깔끔하고 쉽게 풀렸다. 애초에 문제를 잘못 이해 했을 경우에도 이렇게 하는게 맞았는데, 깊이 생각 안하고 냅다 풀어버리니까 버퍼에 집착해서 이상하게 풀게 됐던 듯.

test case에서 걸려서 다행이지 hidden case에만 걸렸으면 진짜 오래 고민했을 것 같다.

 

제출 후에 다른사람들의 풀이를 보니 비슷한 풀이가 많았는데, 내 코드를 줄일 여지를 몇 개 찾아 적어놓는다

1. Comparator를 람다식으로 작성 ==> 가독성 좋아보임 (어차피 간단한 comparator니까)

2. java.util.regex의 Pattern과 Matcher를 활용해 input s에서 숫자만 찾고. matcher의 find와 group 활용

     ==> 추후 regex 공부할 때 알아보고 정리하기

3. Set.add에는 boolean 반환값이 있다. List로 하지 않더라도 if (Set.add(num)) ==> num을 tuple에 저장

    과 같은식으로 할 수 있을 것 같다. (Set.add는 중복아닐 때 true 반환). 물론 이 문제에서는 중복인 경우가 주어지지

    않는다고 했으니 중복검사를 안 하는 List.add가 조금이나마 더 효율적이지 않을까....

 

 

<오늘 배운 점>

1. 문제를 똑바로 읽자 제발

2. 문제를 읽었으면 예시를 떠올려보자. 최대한 내가 생각한 풀이가 틀리게 되는 방향으로 생각해보기

3. toString() 메서드에 대한 이해......(왜 그냥 String으로 반환해주지 않는지 이해해서 유익하긴 했다.)

 

https://school.programmers.co.kr/learn/courses/30/lessons/72412

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

lv.1 어지간한 문제를 다 풀고 lv2로 넘어왔다.

lv.2의 가장 어려운문제들은 어떤지 궁금해서 마지막페이지로 가서 아무거나 누른 문제.

2021 카카오 코테 문제였나보다.

구현도 쉽지 않았지만, 계속 효율성테스트를 실패해서 약 4시간을 고민한 결과 문제를 잘못읽었음을 깨달았다.

query에 들어오는 조건이 순서가 뒤죽박죽일 수  있는줄 알았는데, 순서가 정해져있었다.

결론 : 문제가 길어도 똑바로 읽고 시작하자

 

<두번째 try>

기존 코드 문제 : 쿼리가 어떤 순서로 들어오는지 모른다고 생각을 해서, 쿼리를 split으로 나누고, 각 조건마다 for문을 돌면서 info가 그 조건을 contain하는지 확인했다. 또한, 이 작업을 info의 모든 element에 대해 해야해서 시간이 굉장히 많이 걸렸다.

추가적으로, 문제에 info는 최대 10만, query는 최대 50만의 길이로 위 코드의 이중포문은 최대 50억번을 돌게된다.😂

 

개선방안 : 언어,직군,경력,소울푸드를 구분할 수 있는 key를 만들어서 Map의 key로 사용하고, 해당 key의 value는 List로 선언을 해서, 해당key를 가진 지원자들의 점수를 삽입해주도록 한다.

==> 이렇게 하면 Map.get(key)를 통해 조건들에 해당하는 지원자들의 점수리스트를 가져오고, reverse sort 후 indexOf로 해당 점수를 넘은 사람이 몇명인지 알아낼 수 있을 듯.

 

생각해볼 점 : info에는 모든 정보가 있지만, query에는 누락된 조건이 있을 수 있다.

예를 들어, info상의 "java backend junior pizza" 지원자는 query의 "java and - and - and - 150" 조건에 의해 검색되어야한다. 두 개의 다른 string을 어떻게 같은 조건으로 검색할것인지(Map의 key를 찾을 때 정규표현식을 쓸수있나 ?)

 

<세 번째 Try>

글쓰다가 뒤로가기 눌러서 코드 캡쳐가 사라졌다 ....

 

각 조건마다 1~9의 숫자 string을 주어 조건들의 조합으로 key를 생성하였다.

<info 인코딩>

java backend senior pizza 150 ==> encode_query와 getScore 함수에서 각각

"3579"의 key와 int 150의 숫자로 인코딩하였다.

이처럼 만들어진 (3*2*2*2) = 24개의 key와, ArrayList를 가진 Map을 선언하여

각각의 key에 해당하는 지원자들의 점수를 삽입한뒤, 추후에 있을 탐색을 위해 정렬해두었다.

keyMap :    Map< String, List<Integer>>

 

<query 인코딩>

query도 마찬가지로 인코딩해주었는데, query의 경우 조건이 주어지지 않는 항목들이 있어 정규식으로 처리해주었다.

"- and backend and - and pizza 150"와 같은 query에서 and와 pizza를 지우고, split()의 regex를 "\s+"로 주어 하나 이상의 공백을 기준으로 split하였다. 

"- and backend and - and pizza"  ==> key  : "59"  score : 150

"- and - and - and - 150"              ==> key : ""      score : 150

 

<keySet 검색>

처리된 query string의 각 문자들을 모두 포함하고 있는 key들을 검색하여, 이들 각각의 리스트에서 점수로 걸러내면 정답을 얻을 수 있다고 생각했다.

- Map.keySet()의 각 원소(key)가 contains(Regex)인지 검사한다.

- Regex = "(?=.*" + String.join(")(?=.*", qr.split("")) + ").*"   ==> key가 query의 각 char을 적어도 하나 포함하는지

 

<List 검색>

regex로 얻은 key들의 List를 각각 가져오고, 각 리스트에서 >점수컷 인 사람을 세서 answer에 누적시킨다.

 

<결과분석>

효율성 테스트를 4개 중 2개 통과했다. 이 문제에서 가장 시간이 오래 걸리는 부분은 각 query에 대해서 List를 탐색하는 부분일 것이다. 내가 한 방법대로 하면 쿼리 조건이 빈 것 없이 4개를 채워서 들어오면 탐색을 한 번만 하지만, 

- - - - 150 처럼 점수만 조건으로 들어왔을때는 최대 (3*2*2*2) = 24번의 탐색을 진행하게 된다. 이게 효율성테스트를 통과하지 못하는 직접적인 이유인 듯하다.

 

<다른방법>

조건에 빈 틈이 있어도 탐색횟수를 줄이려면, 빈 조건이 있는 조건까지 key를 생성해주면 된다. 기존 (3*2*2*2)의 key에서 조건이 빠지는 경우까지 생각하여 (4*3*3*3) = 108개의 고유  key를 생성하고, info의 조건 하나마다 포함,불포함하는 경우 총 16가지의 경우의 수에 점수를 모두 삽입해주면 된다.

문제를 보자마자 생각했던 방법인데, 당시에는 "조건 영역의 개수가 늘어나거나, 조건의 option이 한두가지만 추가되어도, 하나의 info에 대해 삽입해야할 Key의 permutation이 기하급수적으로 많아지기 때문에 리소스 측면에서 낭비가 심하다" 라고 생각했다. 내가 간과한 것은 query의 개수가 많으면 많을수록 HashTable을 만드는 것에 대한 기회비용이 작아진다는 것이다. (한 번 잘 만들어놓으면 매 query에 대해 단 한 번의 탐색만 하도록 보장해줄 수 있기 때문)

내가 풀었던 방법은 삽입의 비용이 이걸 상쇄할정도로 매우 크거나, query들의 조건이 빠짐없이 전달된다고 가정했을 때 효과적일 것이라고 생각한다.

 

<최종 풀이>

 

import java.util.*;
class Solution {
    public static int[] solution(String[] info, String[] query) {
        //initialize codeMap
        codeMap.put("cpp",'1'); codeMap.put("java",'2'); codeMap.put("python",'3');
        codeMap.put("frontend",'4'); codeMap.put("backend",'5'); codeMap.put("senior",'6');
        codeMap.put("junior",'7'); codeMap.put("chicken",'8'); codeMap.put("pizza",'9'); codeMap.put("-",'-');
        int[] answer = new int[query.length];
        //Map 생성, key permutation별로 전부 삽입해주기
        Map<String, List<Integer>> keyMap = new HashMap<>();
        for (String inf:info){
            int score = getScore(inf);
            String[] encoded = encode_info(inf);
            for (String key:encoded){
                if (!keyMap.containsKey(key)){
                    keyMap.put(key, new ArrayList<Integer>());
                }
                keyMap.get(key).add(score);
            }
        }
        ///key별로 미리 정렬해놓기
        for (String elem:keyMap.keySet()){
            Collections.sort(keyMap.get(elem));
        }
        //query를 돌면서 이분탐색 진행
        int ansidx = 0;
        for (String qr:query){
            int cut = getScore(qr);
            qr = encode_query(qr);
            if (keyMap.containsKey(qr)){
                List<Integer> lst = keyMap.get(qr);
                answer[ansidx] = countscore(lst, cut);
                ansidx++;
            }
            else{
                answer[ansidx] = 0;
                ansidx++;
            }
        }
        return answer;
    }

    public static Map<String, Character> codeMap = new HashMap<>();

    public static char stringTocode(String code){

        return codeMap.get(code);
    }

    public static String[] encode_info(String info){

        String[] tempsplit = info.replaceAll(" [0-9]+", "").split(" ");
        int idx = 0;
        String[] permutations = new String[16];
        for (int i = 0; i<2; i++){
            char[] encoded = new char[4];
            encoded[0] = (i == 1)?stringTocode(tempsplit[0]):'-';
            for(int j = 0; j<2; j++){
                encoded[1] = (j == 1)?stringTocode(tempsplit[1]):'-';
                for(int k = 0; k < 2; k++){
                    encoded[2] = (k == 1)?stringTocode(tempsplit[2]):'-';
                    for(int n = 0; n<2; n++){
                        encoded[3] = (n == 1)?stringTocode(tempsplit[3]):'-';
                        permutations[idx] = String.valueOf(encoded);
                        idx++;
                    }
                }
            }
        }
        return permutations;
    }

    public static String encode_query(String query){
        String encoded = "";
        String[] tempsplit = query.replaceAll(" [0-9]+","").split(" and ");
        for (String sp:tempsplit){
            encoded += stringTocode(sp);
        }
        return encoded;
    }

    public static int getScore(String info){
        return Integer.parseInt(info.replaceAll("[^0-9*]",""));
    }

   public static int countscore(List<Integer> arr, int score){

    int mid=0;
    int end = arr.size();
    int start = 0;

    while (end > start) // end가 start보다 같거나 작아지면, 그 값이 Lower Bound이므로 반복을 종료한다.
    {
        mid = (start + end) / 2; 
            if (arr.get(mid) >= score) // 중간값이 원하는 값보다 크거나 같을 경우, 끝값을 중간값으로 설정하여 다시 탐색한다.
            end = mid;
        else start = mid + 1; // 중간값이 원하는 값보다 작을 경우, 시작값을 중간값+1로 설정하여 다시 탐색한다.
    }
    return arr.size()-start;

    }
}

조건들을 숫자로 mapping해줄 필요는 없지만, regex 연습할 겸 사용해봤다.

자바의 객체지향에 익숙해지면 리팩토링 해봐야겠다.

 

 

<배운 점>

1. 문제를 잘 읽어야된다

2. 풀이를 구상할 때, 연산량이 큰 메서드가 무엇인지 생각하고, 요구조건에 맞게 설계해야한다(중요)

3. 여러번 풀면서 문자열을 다르게 처리하며 정규표현식 쓰는 연습 해봄 --> 글 따로 정리

4. Map의 Entry element를 List로 하여 저장하는 아이디어

알고리즘 문제를 푸는데, for문 안에서 정규식을 처리하고 특정한 로직을 수행하는데 정규식의 어떤 케이스에서 에러가 나는지 몰라 함수 여러군데에 print문을 찍어 확인하였다.

코드도 지저분해지는 것 같고, 다 찾아서 주석처리해주는 것도 번거로워서 이참에 디버거를 써보기로 함.

중단점을 생성해주고 디버깅 실행하면, 중단점에서 코드를 멈춰준다.

이런식으로 멈춰있는 동안에 변수들의 상태를 확인할 수 있고, 다음 코드를 한 줄씩 실행하면서 이들의 변화를 볼수있다.

하지만, 나에게 발생한 문제는 for문이 한참 돌다가 발생한 것이기 때문에 손으로 한줄씩 넘겨주면서 Exception이 발생한 곳을 찾아줄 수 없었다.

이럴땐, Exception이 발생할 때 멈춰주도록 설정을 하면 된다.

중단점 우클릭하면 나오는 창

 

원하는 BreakPoint에만 체크

이렇게 NullPointerException이 발생했을 경우 멈춰주게되고, 왼쪽 탭에서 한 줄씩 이전으로 돌릴 수도 있다.

한줄 뒤로 돌아왔더니, 정규식을 처리한 이후 strip을 해주면 ""만 남는 케이스가 있어 이것을 넘겨받은 함수에서 해당하는 값을 찾지 못 해 에러가 발생한 것이었다.

 

 

'공부 > 잡다' 카테고리의 다른 글

IntelliJ 콘솔창 한글 깨질때  (0) 2023.04.15
웹 동작 간략개괄  (0) 2023.04.14
Twitter recommendation system code revealed  (0) 2023.04.06
Open-AI ChatGPT plugin  (0) 2023.04.06
ajax async // 웹관련 몇가지  (0) 2023.04.01

https://school.programmers.co.kr/learn/courses/30/lessons/42889

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

스테이지별 실패율을 정렬하고, 그 인덱스를 반환해야 했기에,

Python의 dictionary와 같은 자료형에서 value 기준으로 정렬하면 좋겠다고 생각했다.

 

Java에서 Map은 Map.Entry 클래스들의 Set이다.

Map의 클래스멤버. KeyValueHolder라고 되어있다.

이로써, Map은 (key,value)의 입력이 들어올 때 마다 Map.Entry클래스를 인스턴스화해서 이를 저장하는걸 알수있다.

List< Map.Entry<Integer, Double> > 을 만든 뒤,이를 커스텀 Comparator를 통해 정렬하고, List를 돌면서 stage단계인 Key를 반환하게 하면 될거라고 생각했다.

 

Stage 별 해당 Stage에 머무르고 있는 사람을 Map으로 관리한 뒤,

로직에 맞는 실패율을 구해 entryList에 넣어준다. 삼항연산자는 연습삼아 써봤는데, 가독성이 안좋아 지양해야겠다.

1. stage i에 있는 사람이 0이면 failrate = 0

2. stage i에 사람이 있으면, 그 뒤 스테이지에 있는 사람(stage i를 깬 사람)의 합을 구해 나눗셈으로 실패율 계산

     ==> 이 때, afterstage == 0이면, 뒷 스테이지로 넘어간 사람이 없다는 뜻 + 0으로 나눗셈 안되므로

             failrate = 0으로 설정한다.

 

1.빨간부분이 comparator 정의. o1과 o2는 Map.Entry<Integer, Double>이며 getKey와 getValue 메소드를 사용할 수 있다.

문제의 로직대로 비교한 뒤, List의 sort함수를 사용해 정렬

 

2. Collections.reverseOrder() 메소드는 매개변수로 받은 comparator의 리턴값을 거꾸로 바꾼 Comparator를 반환한다.

 

나머지는 리스트를 돌면서 Key를 배열에 담아주면 끝

 

+ Recent posts