Today's Codekata
// 과일 장수
class Solution {
public int solution(int k, int m, int[] score) {
int answer = 0;
Arrays.sort(score);
for (int i = score.length - m; i >= 0; i-=m) {
int p = score[i];
answer += p * m;
}
return answer;
}
}
for문에서 1씩 더하거나 1씩 빼거나만 생각했었는데, 조금만 생각해보면 더 다양하게 활용이 가능하다는 걸 깨달았다.
// 모의고사
class Solution {
public int[] solution(int[] answers) {
int[][] patterns = {
{1,2,3,4,5},
{2,1,2,3,2,4,2,5},
{3,3,1,1,2,2,4,4,5,5}
};
int[] scores = new int[patterns.length];
for (int i = 0; i < answers.length; i++) {
for (int j = 0; j < patterns.length; j++) {
if (answers[i] == patterns[j][i % patterns[j].length]) {
scores[j]++;
}
}
}
int max = Arrays.stream(scores).max().getAsInt();
List<Integer> answer = new ArrayList<>();
for (int i = 0; i < scores.length; i++) {
if (scores[i] == max) answer.add(i + 1);
}
return answer.stream().mapToInt(i -> i).toArray();
}
}
이번 문제는 정답에 가깝게 가긴 했으나 도저히 답을 못 찾아서, 힌트를 받아서 문제를 풀어보고 그 이후로 여러 차례 리팩토링해보면서 공부해봤다. 각각의 배열을 작성했었는데 행렬로 합쳐서도 만들어보고, 각각의 수포자들 점수를 세기 위해 나눠져있던 for문도 하나로 만들어봤다. `Math.max()`를 두번 써서 찾았던 최고값도 스트림으로 만들어보고, 배열로 반환하던걸 리스트로 만들어보기도 했다. 어떤 코드가 좋은지에 대한 최종 답안은 결국 성능일까? 아니면 누가 봐도 명확한 코드일까? 이런 고민을 가져보는 시간이었다.
-- 자동차 대여 기록에서 대여중 / 대여 가능 여부 구분하기
SELECT CAR_ID,
CASE WHEN MAX(CASE WHEN '2022-10-16' BETWEEN START_DATE AND END_DATE THEN 1 ELSE 0 END) = 1
THEN '대여중'
ELSE '대여 가능' END AS AVAILABILITY
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
GROUP BY CAR_ID
ORDER BY CAR_ID DESC;
-- 년, 월, 성별 별 상품 구매 회원 수 구하기
SELECT YEAR(OS.SALES_DATE) AS YEAR,
MONTH(OS.SALES_DATE) AS MONTH,
UI.GENDER,
COUNT(DISTINCT OS.USER_ID) AS USERS
FROM USER_INFO UI
INNER JOIN ONLINE_SALE OS ON UI.USER_ID = OS.USER_ID
WHERE UI.GENDER IS NOT NULL
GROUP BY 1, 2, 3
ORDER BY 1, 2, 3;
-- 서울에 위치한 식당 목록 출력하기
SELECT I.REST_ID, I.REST_NAME, I.FOOD_TYPE, I.FAVORITES, I.ADDRESS,
ROUND(AVG(R.REVIEW_SCORE), 2) AS SCORE
FROM REST_INFO I
JOIN REST_REVIEW R ON I.REST_ID = R.REST_ID
WHERE I.ADDRESS LIKE '서울%'
GROUP BY 1
ORDER BY 6 DESC, 4 DESC;
`WHERE '2022-01-01' BETWEEN start_date AND end_date` 여기처럼 값을 앞에 두고 컬럼을 뒤에 두는 방식도 가능함을 배웠다.
Today I Learned
일정 관리 앱
일정 CRUD & 유저 CRUD
/**
* Author DTO
*
* 이 클래스는 일정에서 작성자 정보를 표현하기 위한 데이터 전송 객체입니다.
* User 엔티티에서 필요한 필드만 추출하여 응답에 포함합니다.
*
* 주요 필드:
* - id: 작성자의 고유 식별자
* - username: 작성자의 사용자 이름
*/
@Getter
@AllArgsConstructor
public class Author {
private Long id;
private String username;
public static Author from(User user) {
return new Author(user.getId(), user.getUsername());
}
}
@Getter
@AllArgsConstructor
public class ScheduleResponse {
private final Long id;
private final String title;
private final String content;
private final Author author;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
public static ScheduleResponse from(Schedule schedule) {
return new ScheduleResponse(
schedule.getId(), schedule.getTitle(), schedule.getContent(),
Author.from(schedule.getUser()), schedule.getCreatedAt(), schedule.getModifiedAt());
}
}
일정 CRUD와 유저 CRUD를 각각 만들어내는 건 크게 어려움이 없었다. 그 과정 중에 정적 팩토리 메서드도 활용함으로써 코드가 간결해졌고, 개발 과정에서도 편리함을 느낄 수 있었다. 가장 까다로운 부분은 두 테이블 간의 연관관계 매핑이었다. 먼저 `Schedule` 엔티티에 `@ManyToOne` 어노테이션을 달아주고 `@JoinColumn(name = "user_id")` 어노테이션으로 `User` 엔티티의 고유 식별자와 연결해주었다. 이후 요청 DTO에서는 `username` 대신 `userId`를 받도록 수정했고, 응답 DTO에서는 `userId`와 `username` 모두 포함해야 했기 때문에 별도의 `Author` DTO를 만들어서 `User` 엔티티에서 피룡한 필드만 추출해서 응답에 포함시켰다.
마치며
현재 회원가입과 로그인(인증) 구현을 위해서 한참을 다양한 방향으로 시도해보고 있다. `Filter` 클래스를 직접 만들고 `@Configuration` 어노테이션을 붙인 `Config` 클래스를 통해 요청이 컨트롤러로 전달되기 전에 필터를 거치도록 설정했다. 하지만 응답 메시지가 이상하게 출력되고, 모든 요청에 대해 "로그인 해주세요."라는 메시지만 반복적으로 콘솔에 찍히는 문제가 발생했다.필터를 만들고 적용하는 과정에서 문제가 생긴 것 같은데, 내일은 `Session`과 `Filter`의 동작 원리에 대해서 제대로 이해하고 직접 설명할 수 있을 정도로 구조를 정리해보는 시간을 가져야겠다. 코드를 더 꼼꼼하게 살피고, 인증 흐름을 명확하게 파악하는 것이 목표다.