문제

문제

 

처음 작성한 코드

def solution(s):
    answer = []
    s = sorted(s[2:-2].split("},{"), key=lambda x: len(x))
    if len(s)==1:
        return list(map(int, s))
    for i in range(len(s)-1):
        if i==0: answer.append(s[i])
        answer.append(''.join(set(s[i+1].split(",")) - set(s[i].split(','))))
    return list(map(int,answer))

 

코드 리뷰 후

def solution(s):
    import re
    from collections import Counter
    return list(map(int, [i for i, v in sorted(Counter(re.findall('\d+',s)).items(), key=lambda x: x[1],reverse=True)]))

 

깨달은 점

  • 숫자만 빼고 싶을 때 정규식 패턴을 '\d+'로 하면 된다.
  • 잘하는 사람들 진짜 많다..

이번 강의에서는 AOP 기술을 알아보았다.

 

AOP란 무엇인가?

예시를 들어서 설명해보겠다.

내가 다니는 회사의 악덕사장님이 우리 회사 코드의 모든 메소드의 실행시간을 알고싶다고 하신다..

그럼 모든 메소드의 첫 시작 시간과 끝나는 시간의 차이를 구해서 실행시간을 알 수 있다. 말이야 간단하지

메소드가 193824개라면? 오늘까지 하라면? 절망적인 하루가 될 것이다..

우리 시스템의 핵심 관심 사항은 회원가입을 하고, 회원 list를 볼 수 있는 기능인데

모든 메소드의 공통 관심 사항은 실행 시간을 구하는 기능인 것이다.

만약 위에서 말한대로 코드를 짠다면 핵심 관심 사항과 공통 관심 사항이 섞여서 아주 복잡한 코드가 될 것이다.

또한 실행시간을 ms 단위 말고 s 단위로 바꿔봐! 하는 순간 다시 모든 메소드에 가서 단위를 변경해줘야하는 끔찍한 일이 생길 것이다..

하지만 AOP를 사용하면 핵심 관심 사항과 공통 관심 사항을 분리하여 원하는 적용 대상에 적용만 시키면 유지보수도 쉽고 깔끔한 코드가 완성될 수 있다.

아참.. AOP는 Aspect-Oriented Programming의 약자이다..^^

 

AOP 적용 전 코드

모든 메소드에 시간을 구할 수 있는 코드를 작성했다. 2개만 작성했는데도 한숨이 나왔다.. 똑같은 코드가 내눈에 벌써 두개나 보인다.. 윽윽

 

AOP 적용 후

TimeTraceAop.java

따로 클래스를 두어 진행한다.

  • @Component 어노테이션을 통해 빈에 등록을 한다. 지금까지 했던 것처럼 따로 @Bean을 통해 등록할 수도 있지만 이번 강의에서는 @Component를 사용했다.
  • @Aspect 는 AOP를 적용시키기위해 필요한 어노테이션이다.
  • execute() 위에 @Around 어노테이션을 추가한다. 이는 적용 대상을 지정하는 것이다. hello.helloSpring 아래에 모든 메소드에 적용한다는 의미이다.

AOP 작동 원리

강의코드를 기준으로 설명을 하자면, 회원가입한 회원들의 목록을 볼 수 있는 기능이 있다.

그렇게 되면 아래와 같이 동작을 한다.

  1. 컨트롤러가 페이지 띄운다.
  2. 서비스가 레포지토리를 통해 회원 목록을 가져온다.

그러면 Controller가 Service를 호출하고 Serivce가 Repository를 호출할까? AOP를 적용하면 그렇지 않게된다.

AOP를 적용하면 스프링 부트는 프록시라는 기술을 통해 가짜 controller를 빈에 등록하고 그 가짜 controller가 진짜 controller를 부르는 식이 되는 것이다.

위의 AOP 코드를 보면 joinPoint.proceed()가 있는 데 이 함수를 통해 가짜가 진짜를 부르는 것이라고 한다.

김영한 강사님 pdf에서 캡쳐

 

문제

문제

https://programmers.co.kr/learn/courses/30/lessons/17677

 

코딩테스트 연습 - [1차] 뉴스 클러스터링

뉴스 클러스터링 여러 언론사에서 쏟아지는 뉴스, 특히 속보성 뉴스를 보면 비슷비슷한 제목의 기사가 많아 정작 필요한 기사를 찾기가 어렵다. Daum 뉴스의 개발 업무를 맡게 된 신입사원 튜브

programmers.co.kr

 

 

처음 작성한 코드

def solution(str1, str2):
    import re
    list1 = [(str1[i:i+2]).lower() for i in range(len(str1)-1) if re.match('[a-zA-Z]{2}',str1[i:i+2])]
    list2 = [(str2[i:i+2]).lower() for i in range(len(str2)-1) if re.match('[a-zA-Z]{2}',str2[i:i+2])]
    gyo = 0
    if not list1 and not list2: return 65536
    for i in list1:
        if i in list2:
            gyo+=1
            list2[list2.index(i)] = '$$$'
    hap = len(list1+list2)-gyo
    return int((gyo/hap)*65536)

 

코드 리뷰 후

def solution(str1,str2):
    import re
    list1 = [(str1[i:i+2]).lower() for i in range(len(str1)-1) if re.match('[a-zA-Z]{2}',str1[i:i+2])]
    list2 = [(str2[i:i+2]).lower() for i in range(len(str2)-1) if re.match('[a-zA-Z]{2}',str2[i:i+2])]
    
    if not list1 and not list2: return 65536

    gyo = set(list1) & set(list2)
    hap = set(list1) | set(list2)

    _gyo = sum([min(list1.count(g), list2.count(g)) for g in gyo])
    _hap = sum([max(list1.count(h), list2.count(h)) for h in hap])
    return int(_gyo/_hap*65536)

 

깨달은 점

  • 정규식 좋다
  • 다중집합은 원소의 중복을 허용한다.. 그래서 set()을 사용하지 않고 코딩을 했는데 다른 사람들이 푼 방법을 보니까 기가막힌다! 진짜 어떻게 저런 생각을 하지..

'Programmers' 카테고리의 다른 글

[프로그래머스] 실패율  (0) 2021.01.13
[프로그래머스] 튜플  (0) 2021.01.11
[프로그래머스] H-Index  (0) 2021.01.09
[프로그래머스] 크레인 인형뽑기 게임  (0) 2021.01.07
[프로그래머스] 네트워크  (0) 2021.01.06

스프링 JdbcTemplate

  • JdbcTemplate을 이용하여 구현체를 만들면 코드가 확~ 줄어든다. 반복 코드를 제거해주기 때문이다. 하지만 SQL 문은 직접 작성해줘야한다. ㅜ.ㅜ
  • 쿼리문을 작성하여 db에서 데이터를 가져올 때는 jdbcTemplate.query()를 사용해서 RowMapper로 감싸준다음 가져올 수 있다.

jdbcTemplate.query(), RowMapper<Member>

  • insert를 위한 기능은 SimpleJdbcInsert()를 사용하면 된다. usingGenerateKeyColumns에는 pk 컬럼명을 넣어주면 된다. executeAndReturnKey는 쿼리문을 실행해서 레코드를 Map을 통해 만든 형식에 따라 가져와준다.

SimpleJdbcInsert()

 

JPA

  • 객체를 테이블로 mapping 해주는 프레임 워크.
  • JPA는 인터페이스다. 그러므로 구현체가 필요한데 이러한 구현체에 Hibernate라는 것이 있고 이 강좌에서는 이를 사용한다.
  • JPA를 사용하면 JdbcTemplate을 사용할 때 불편했던 바로바로 sql문을 작성하지 않아도 된다. JPA가 기본적인 SQL을 직접 만들어서 실행시켜준다.

em.persist()

  • 모든 쿼리문을 쓰지 않아도 가능한 것은 아니다. 위의 insert나 pk(기본키)를 기준으로 데이터를 찾는 기능은 구현되어있지만, 그 외의 기능은 직접 쿼리문을 JPQL로 짜줘야한다.

em.find() -> 기본키를 이용하여 데이터 찾기
em.createQuery()

  • JPA는 통해 데이터를 변경하려면 트랜잭션 안에서 실행해야한다. 따라서 @Transactional 어노테이션을 service 클래스 위에 써준다.

@Transactional

  • JPA는 엔티티 매니저(EntityManager)로 모두 동작한다. build.gradle에 data-jpa를 가져오면 spring boot가 EntityManager를 생성하고 db랑 연결을 하고 이런 모든 동작을 해준다.

data-jpa

  • 우리는 생성된 EntityManager를 가져와서 사용하면 된다. 여기서 @Autowired 안해줘도 가져올 수 있는 이유는 클래스에 생성자가 하나일때는 @Autowired안해도 알아서 해준다고 한다... 꿀팁~

Entitymanager, constructor

  • 도메인 객체 위에 @Entity 어노테이션을 써서 mapping 시켜줘야 한다. 또한 pk를 알려주기 위해 @Id 라는 어노테이션도 써줘야하고, id는 시스템상에서 1씩 증가해서 db에 넣어주기 때문에(이를 IDENTITY 방식이라 함) GenerationType.IDENTITY도 추가해야한다.

@Entity, @Id, @GenerateValue(strategy = GenerationType.IDENTITY)

  • JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다. -> 개발 생산성 크게 향상

 

스프링 데이터 JPA

  • 스프링 데이터 JPA는 인터페이스다. 구현체를 구현해야 사용할 수 있지만 얘는 뭔가 다르다. 필요없다.. 즉 구현체 없이 인터페이스 클래스 하나로 사용이 가능하다는 것이다.
  • 스프링 데이터 JPA가 자동적으로 스프링 빈에 등록해준다.
  • 사용법은 JpaRepository(인터페이스)를 extends(상속) 해주면 된다. jpaRepository<T, ID> 형식으로 써주면 된다. T는 테이블(?), ID는 PK(기본키) 데이터  자료형을 써주면 된다.

스프링 데이터 JPA 구현 끝...

  • JpaRepository를 타고 가보면 구현하려던 메쏘드들이 이미 구현이 되어있다~

 

깨달은 점

스프링 데이터 JPA 짱이다..

문제

문제

 

처음 작성한 코드

def solution(citations):
    citations.sort(reverse=True)
    for i in range(len(citations)):
        if citations[i]<=i+1:
            return i
    return len(citations)

위 수식보고 풀었음 (위키피디아 고마워~)

 

깨달은 점

  • 프로그래머스 문제 설명 개거지같다 진짜 보면서 개화남
  • 따로 링크 준 이유를 알아냄
  • 뻐킹뻐킹 개화남
  • 문제는 easy

 

문제

문제

 

처음 작성한 코드

def solution(board, moves):
    answer = 0
    stack = []
    for i in moves:
        for j in board:
            if j[i-1]!=0:
                if len(stack)==0 or stack[-1]!=j[i-1]:
                    stack.append(j[i-1])
                else:
                    stack.pop()
                    answer+=2
                j[i-1]=0
                break
    return answer

 

깨달은 점

  • 카카오 개발자 겨울 인턴십 문제 중 제일 쉬운 문제인 것 같다..
  • 잘 푼 것 같다

구조

 

코드

//HomeController.java
package hello.helloSpring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(){
        return "home";
    }
}
//MemberController.java
package hello.helloSpring.controller;

import hello.helloSpring.domain.Member;
import hello.helloSpring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@Controller
public class MemberController {
    private final MemberService memberService;

    @Autowired // 스프링이 뜰 때, 컨테이너에서 관리하는 memberService 객체와 연결해준다.
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @GetMapping("/members/new")
    public String createMemberForm(){
        return "/members/createMemberForm";
    }

    @PostMapping("/members/new")
    public String create(MemberForm memberForm){
        Member member = new Member();
        member.setName(memberForm.getName());

        memberService.join(member);
        return "redirect:/";
    }

    @GetMapping("/members")
    public String list(Model model){
        List<Member> members = memberService.findMembers();
        model.addAttribute("members",members);
        return "members/memberList";
    }
}
//MemberForm
package hello.helloSpring.controller;

public class MemberForm {
    private String name;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

html 파일들은 생략..

 

결과

홈화면
회원가입 화면
회원 목록 화면

 

고작 이거 만드는 게 이렇게 힘들다니~

컴포넌트 스캔과 자동 의존관계 설정

스프링을 처음 실행시킬 때, 스프링 컨테이너라는 통이 하나 생긴다.

우리가 아무렇지 않게 클래스 위에 @Controller 이런식으로 쓰는 어노테이션들은 이 클래스 객체를 컨테이너에 생성해서 넣어주고 컨테이너에서 관리하겠다는 뜻으로 쓰는 것이다.

이것을 스프링 컨테이너에서 스프링 빈이 관리된다고 표현한다.

 

스프링이 관리를 하게 되면 이제부터는 다~ 스프링 컨테이너에 등록하고 컨테이너로부터 받아서 써야한다.

즉, 컨테이너안에서 모든 것을 해결해야한다.

 

저번 시간까지 service와 repository를 만들었는데 이제 뷰를 사용자에게 보여주기 위해 controller를 만들어야한다.

Controller, Service, Repository는 정형화된 패턴이다.

Controller를 통해서 외부의 요청을 받고

Service에서 비즈니스 로직을 만들고

Repository에서 데이터를 저장한다.

 

여기서 보면 Controller는 요청을 받아서 Service를 통해 회원가입을 하고, 조회를 할 것이다. 이 때, 의존관계가 있다고 표현한다. 의존관계가 있으므로 Controller와 Service를 연결해야하고 이를 위해 생성자 위에 @Autowired라는 어노테이션을 쓰면 자동으로 연결해준다.

자동으로 연결해준다는 의미는 아래 예시 코드를 통해 보여주도록 하겠다.

package hello.helloSpring.controller;

import hello.helloSpring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MemberController {
    private final MemberService memberService;

    @Autowired // 스프링이 뜰 때, 컨테이너에서 관리하는 memberService 객체와 연결해준다.
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}

스프링이 시작될 때, 스프링은 @Controller를 보고 MemberController가 컨트롤러 역할이구나 라고 생각하고 객체를 생성하여 컨테이너에 넣는다. (==빈을 생성한다.)

이 때, Controller는 Service를 통해 기능을 수행하므로 연관 관계가 있다. 이를 위해 @Autowired를 생성자 위에 써주면 이 Controller 객체를 생성할 때 생성자를 통해 컨테이너에 존재하는 MemberService를 넣어주면서 빈이 생성되는 것이다.

한 마디로 말하자면, 스프링은 @Controller를 보고 Controller 객체를 생성하고 그 때 생성자를 호출하면서 스프링 컨테이너에 있던 MemberService를 가져다가 넣어주는 것이다. 이해 완료?!!?

 

여기서 주의할 점은 저번 시간에 한 코드에서 위의 코드를 추가한다면 에러가 난다.

에러

왜냐하면 이전 코드의 MemberService 클래스는 컨테이너에 빈으로 등록되지 않았기 때문이다. (어노테이션 안썼음)

따라서 MemberService 클래스 위에 @Service라는 어노테이션을, 똑같은 방식으로 MemoryMemberRepository 클래스 위에 @Repository어노테이션을 쓰면 스프링이 시작될 때 컨테이너에 넣어서 관리가 된다.

위와 같은 @Controller, @Service, @Repository 를  컴포넌트 스캔이라고 하며

@Autowired를 자동 의존관계 설정이라고 한다.

 


자바 코드로 직접 스프링 빈 등록하기

어노테이션을 쓰지 않고 직접 코드로 등록하기 위해 @Controller를 제외한 @Service, @Repository, @Autowired를 다 지우고 시작한다.

 

1. hello.helloSpring 밑에 SpringConfig 클래스를 만들어준다.

구조

2. 코드를 작성해준다.

package hello.helloSpring;

import hello.helloSpring.repository.MemberRepository;
import hello.helloSpring.repository.MemoryMemberRepository;
import hello.helloSpring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}

@Configuration을 통해 스프링에게 알리고 @Bean을 통해 빈을 등록하겠다고 하면 된다.

MemberService를 생성할때는 memberRepository가 필요한데(연관 관계) 이때 memberRepository()를 호출해주면 된다.

 

직접 코드를 작성하는 것의 장점

현재 어떤 db를 사용할 것인지 정해져있지 않은 상황이라고 가정이 되어있다. 나중에 db가 결정되면 저 Repository만 바꿔주면 되는 것이다. 즉, 다른 코드 손 볼 필요 없이 return new dbMemberRepository()와 같은 다른 레포지토리를 생성해주면 된다.

 

재밌다!

문제

문제

 

처음 작성한 코드

def solution(n, computers):
    answer = 0
    visited = []
    stack = [0]
    all_stack = set([i for i in range(n)])
    while True:
        if len(visited)==n:
            return answer+1
        if len(stack)==0:
            answer+=1
            stack.append(list(all_stack-set(visited))[0])
        a = stack.pop()
        if a not in visited:
            visited.append(a)
        for j in range(n):
            if computers[a][j]==1 and a!=j and j not in visited:
                stack.append(j)
  1. 탈출조건: 모든 노드를 다 방문했을 때
  2. len(stack)이면 네트워크가 끊긴거라 answer+=1 하고 visited 안한 노드를 stack에 넣고 그 노드가 속해있는 네트워크를 다시 돈다.
  3. stack(방문해야할 노드)에서 pop()하여 방문안했으면 방문을 하고, 노드의 이웃노드들을 stack에 넣는다. (왜냐면 방문해야하니까~)

깨달은 점

  • 문제를 통과하긴 했는데 DFS인지 BFS인지 모르겠다..
  • 네트워크가 끊기면 다음 방문할 노드를 어떻게 정할까 고민했었는데 set을 이용해서 all_stack에서 지금까지 방문한 노드의 리스트를 빼고 그 중 가장 앞에 있는 노드로 정했다. list는 빼기가 안되는데 set은 된다. set은 인덱스로 접근이 안되서 다시 list()로 변환하고 인덱스 접근을 시도해야한다.
  • 오늘은 쉽게 푼 것 같다.

'Programmers' 카테고리의 다른 글

[프로그래머스] H-Index  (0) 2021.01.09
[프로그래머스] 크레인 인형뽑기 게임  (0) 2021.01.07
[프로그래머스] 타겟 넘버  (0) 2021.01.05
[프로그래머스] 다리를 지나는 트럭  (0) 2021.01.05
프린터  (0) 2021.01.03