Dynamic Programming(3)

인프런의 영리한 프로그래밍을 위한 알고리즘 강좌를 보고 작성한 문서입니다.


동적계획법

  • 일반적인 최적화문제(optimistation problem) 혹은 카운팅(counting) 문제에 적용됨

  • 주어진 문제에 대한 순환식(recurrence equation)을 정의한다.

  • 순환식을 memorization 혹은 bottom-up 방식으로 푼다.


핵심은 순환식

  • subproblem들을 풀어서 원래 문제를 푸는 방식, 그런 의미에서 분할정복법과 공통성이 있음

  • 분할정복법에서는 분할된 문제들이 서로 disjoint하지만 동적계획법에서는 그렇지 않음

  • 즉 서로 overlapping하는 subproblem들을 해결함으로써 원래 문제를 해결


분할정복법 vs 동적계획법

분할정복법

행렬경로문제

Optimal Substructure

  • 어떤 문제의 최적해가 그것의 subproblem들의 최적해로부터 효율적으로 구해질 수 있을 때 그 문제는 optimal substructure를 가진다고 말한다.

  • 분할정복법, 탐욕적기법, 동적계획법은 모두 문제가 가진 이런 특성을 이용한다.


Optimal Substructure를 확인하는 질문

optimal_substructure를_확인하는_질문

최장경로 문제

  • 노드를 중복 방문하지 않고 가는 가장 긴 경로

  • optimal substructure를 가지는가?

    최장경로문제

    최장경로문제2

Spring Security를 기존 프로젝트에 적용하기

기존 프로젝트에 Spring Security를 적용 해보자.

Spring Security 적용하기

Maven

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Gradle

compile("org.springframework.boot:spring-boot-starter-security")
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

}

WebSecurityConfig를 만들면 모든 접근에 대해 인가가 필요하게 됩니다.

인증(Authentication) : 해당 사용자가 본인이 맞는지 확인하는 절차.
인가(Authorization) : 인증된 사용자가 요청된 자원에 접근 가능한지를 결정하는 절차.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  	protected void configure(HttpSecurity http) throws Exception {
  		http.csrf().disable();
  		http.headers().frameOptions().disable();
  		http.httpBasic();

  		http
  			.authorizeRequests()
  			.antMatchers("/", "/create", "/login**", "/logout**")
  			.permitAll()
  			.antMatchers("/boards/**", "/myboards", "/api/boards**", "/api/decks/**", "/api/boards/**", "/api/users**")
  			.hasRole("USER");

  }
}

자신이 설정 할 정책들을 적용 합니다.

위와 같이 설정 해두면 .hasRole("USER")에 해당 하는 곳들은 권한이 없는 사용자는 접근이 불가능 하게 됩니다.

(조금 있다 .hasRole("USER")에 해당 하는 롤을 만들 겁니다)

만약 인가때문에 문제가 된다면 일단은 .permitAll()으로 모두를 설정 해두고 작업을 하면 됩니다.


기존 유저 모델

@Getter
@Setter
@Entity
@EqualsAndHashCode(of = "uid")
@ToString
public class Member {

	@Id
	@GeneratedValue
	@Column(name = "MEMBER_ID")
	private long id;

	@Size(min = 3, max = 20)
	@Column(nullable = false, length = 20)
	private String name;

	@Column(nullable = false)
	@JsonIgnore
	private String password;

	@Email
	@Size(min = 6, max = 50)
	@Column(unique = true, nullable = false, length = 50)
	private String email;
}

일단 Role을 만들어야 합니다.

@Getter
@Setter
@Entity
@Table(name = "member_roles")
@ToString
public class MemberRole {
	public static final String USER = "USER";

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private long id;

	private String roleName;

	public MemberRole() {
	}

	public MemberRole(String roleName) {
		this.roleName = roleName;
	}

그리고 위의 Member에 적용 되어야 할 롤들을 추가합니다.

@Getter
@Setter
@Entity
@EqualsAndHashCode(of = "uid")
@ToString
public class Member {

	@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinColumn(name="member")
	private List<MemberRole> roles = new ArrayList<>();

}

cascade=CascadeType.ALL은 엔티티들의 영속관계를 한 번에 처리하지 못하기 때문에 설정 하는 것이고, fetch=FetchType.EAGERmembermember_roles 테이블을 둘 다 조회해야 하기 때문에 즉시 로딩을 이용해서 조인을 하는 방식으로 처리 합니다.


자 이제 재료는 다 준비 되었으니 회원 가입 부분을 수정 해 봅시다.

@Controller
@RequestMapping("/member")
public class MemberController {

	@Resource(name = " memberRepository")
	private MemberRepository memberRepository;

	@PostMapping("")
	public String create(Member member) {
		MemberRole role = new MemberRole();
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		member.setPassword(passwordEncoder.encode(member.getPassword()));
		role.setRoleName("USER");
		member.setRoles(Arrays.asList(role));
		memberRepository.save(member);
		return "redirect:/";
	}

Spring Security에서 지원해 주는 BCryptPasswordEncoder를 이용하면 손쉽게 비밀번호를 암호화 할 수 있습니다.

MemberRole을 정의해서 Member에 넣어주고 save를 합니다.

<div class="signup-box z-depth-2">
     <h6 id="signUpFail" ></h6>
         <h4>Create a Account</h4>

         <form class="signup-form" action="/members" method="POST">
           <div class="row">
               <div class="input-field col s12">
                 <input id="name" name="name" type="text" class="validate">
                 <label for="name">Username</label>
               </div>
           </div>

           <div class="row">
               <div class="input-field col s12">
                   <input id="email" name="email" type="email" class="validate">
                   <label for="email">Email</label>
                 </div>
               </div>

          <div class="row">
               <div class="input-field col s12">
                 <input id="password" name="password" type="password" class="validate">
                 <label for="password">Password</label>
               </div>
          </div>
             <input class="signup-btn waves-effect waves-light btn" type="submit" value="가입하기" />
         </form>

     </div>

위와 같이 하면 이제 Role을 가지고 있는 멤버가 생성 됩니다.


로그인 구현 하기

회원 가입은 잘 되는데 로그인이 안돼서 아직 제대로 된 확인이 안됩니다. 그럼 본격 적으로 로그인 기능을 구현 해 봅시다.

WebSecurityConfig에서 AuthenticationManagerBuilder를 주입 해서 인증 처리를 해야 합니다.

@Resource(name="securityMemberService")
private SecurityMemberService securityMemberService;


public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
  auth.userDetailsService(securityMemberService).passwordEncoder(passwordEncoder());
}

위와 같이 설정 하면 UserDetailsService를 구현한 securityMemberServiceHttpSecurity 객체가 사용하게 되면서 우리가 만든 인증 로직을 바탕으로 동작 하게 됩니다.

그럼 securityMemberService를 제작해 봅시다.

securityMemberService

(주의 : 저같은 경우는 email로 로그인을 합니다.)

@Service("securityMemberService")
public class SecurityMemberService implements UserDetailsService{

	@Resource(name="memberRepository")
	private MemberRepository memberRepository;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		Member member = memberRepository.findByEmail(username).orElseThrow(() -> new UsernameNotFoundException("아이디 혹은 비밀번호를 잘 못 입력 하셨습니다."));
		return new SecurityMember(member);
	}
}

저같은 경우는 email을 통해 로그인을 하기때문에 받아온 username에 email이 담겨 있습니다. 그리고 그걸 이용하여 db에서 email을 통해 member를 가져와서 로그인을 절차를 거치게 됩니다.

위와 같은 방법으로 로그인시 Id 혹은 email등 자신이 원하는 형태를 설정 할 수 있습니다.

UserDetailsService 인터페이스를 보면

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

UserDetails를 반환하는 하나의 메소드만을 가지고 있습니다. 그렇기때문에 기존에 구현한 Member와는 타입이 맞지 않습니다. 이를 해결 하기 위한 방법 중 Spring Security의 User를 상속하는 새로운 멤버 클래스를 추가 합니다. (이때문에 Member가 아닌 User라는 이름으로 클래스를 만들면 문제가 생길수 있습니다)

SecurityMember

@Getter
@Setter
public class SecurityMember extends User{
	private static final long serialVersionUID = 1L;
	private static final String ROLE_PREFIX = "ROLE_";

	public SecurityMember(Member member) {
		super(member.getEmail(), member.getPassword(), makeGrantedAuthority(member.getRoles()));
	}

	private static List<GrantedAuthority> makeGrantedAuthority(List<MemberRole> roles){
		List<GrantedAuthority> list = new ArrayList<>();
		roles.forEach(
			role -> list.add(
					new SimpleGrantedAuthority(ROLE_PREFIX + role.getRoleName())));
		return list;
	}
}

위에도 언급했듯이 저같은 경우는 email로 로그인을 하기때문에 User클래스에 생성자의 username 위치에 member.getEmail()를 사용합니다.

다시 WebSecurityConfig

@Configuration  //이번에는 추가해 주자
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Resource(name="securityMemberService")
	private SecurityMemberService securityMemberService;

  @Override
  	protected void configure(HttpSecurity http) throws Exception {
  		http.csrf().disable();
  		http.headers().frameOptions().disable();
  		http.httpBasic();

  		http
  			.authorizeRequests()
  			.antMatchers("/", "/create", "/login**", "/logout**")
  			.permitAll()
  			.antMatchers("/boards/**", "/myboards", "/api/boards**", "api/login**", "/api/decks/**", "api/boards/**", "/api/boards/**", "/api/users**")
  			.hasRole("USER");

  		http
  			.formLogin()
  			.loginPage("/login")
        .defaultSuccessUrl("/")
        .failureUrl("/login");

  		http
  			.logout()
  			.logoutUrl("/logout")
  			.invalidateHttpSession(true);

  }

  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
    auth.userDetailsService(securityMemberService).passwordEncoder(passwordEncoder());
  }
}

loginPage : 로그인 뷰 페이지

loginProcessingUrl : Post로 로그인을 처리할 Url

defaultSuccessUrl : 로그인 성공 후 이동할 페이지

failureUrl : 실패 후 이동할 페이지

login.html

<div class="login-box z-depth-2">

		<h6 id="loginFail" ></h6>
		<h4>Login To Trello</h4>

		<form class="login-form" action="/login" method="POST">
			<div class="row">
				<div class="input-field col s12">
					<input id="username" name="username" type="email" class="validate">
					<label for="username">Email</label>
				</div>
			</div>
			<div class="row">
				<div class="input-field col s12">
					<input id="password" name="password" type="password"
						class="validate"> <label for="password">Password</label>
				</div>
			</div>
			<button class="login-btn waves-effect waves-light btn" type="submit">로그인</button>
		</form>

		<p class="alternatives-separator">or</p>

		<a class="github-login waves-effect waves-light btn" href="#">
			<div class="github-icon"></div>
			<div class="github-login-text">github로 로그인</div>
		</a>

</div>

Spring Security의 기본 설정으로 인해 지금은 name 속성을 usernamepassword로 해둬야 제대로 동작 합니다.

Dynamic Programming(2)

인프런의 영리한 프로그래밍을 위한 알고리즘 강좌를 보고 작성한 문서입니다.


행렬 경로 문제

  • 정수들이 저장된 nxn 행렬의 좌상단에서 우하단까지 이동한다. 단 오른쪽이나 아래쪽 방향으로만 이동할 수 있다.

  • 방문한 칸에 있는 정수들의 합이 최소화되도록 하라.

    행렬 경로

  • Key Observation

    key observation

  • 순환식

    순환식

  • Recursive Algorithm

    int mat(int i, int j){
      if ( i == 1 && j == 1)
        return m[i][j];
      else if ( i == 1)
        return mat(1, j-1) + m[i][j];
      else if ( j == 1)
        return mat( i - 1, 1) + m[i][j];
      else
        return Math.min(mat( i - 1, j ), mat( i, j - 1 )) + m[i][j];
    }
    
  • Memoization

    int mat(int i, int j){
      if ( L[i][j] != -1 ) return L[i][j];
      if ( i == 1 && j == 1 )
        L[i][j] = m[i][j];
      else if ( i == 1)
        L[i][j] = mat( 1, j-1 ) + m[i][j];
      else if ( j == 1 )
        L[i][j] = mat( i - 1 , 1 ) + m[i][j];
      else
        L[i][j] = Math.min( mat( i - 1 , j ), mat( i, j - 1 ) ) + m[i][j];
      return L[i][j];
    }
    
  • Bottom-up

    bottom-up 방식

    int mat(){
      for ( int i = 1; i <= n; i++ ){
        for ( int j = 1; j <= n; j++ ){
          if ( i == 1 && j == 1 )
            L[i][j] = m[1][1];
          else if ( i == 1 )
            L[i][j] = m[i][j] + L[i][j-1];
          else if ( j == 1 )
            L[i][j] = m[i][j] + L[i-1][j];
          else
            L[i][j] =  m[i][j] + Math.min( L[i-1][j] , L[i][j-1] );
        }
      }
      return L[n][n];
    }
    
    • Common Trick

      // initialise L with L[0][j]=L[i][0]=무한대 for all i and j
      int mat(){
        for ( int i = 1; i <= n; i++ ){
          for (int j = 1; j <= n; j++ ){
            if ( i == 1 && j == 1 )
              L[i][j] = m[1][1];
            else
              L[i][j] =  m[i][j] + Math.min( L[i-1][j] , L[i][j-1] );
          }
        }
        return L[n][n];
      }
      

경로 구하기

경로구하기

// initialise L with L[0][j]=L[i][0]=무한대 for all i and j
int mat(){
  for ( int i = 1; i <= n; i++ ){
    for ( int j = 1; j <= n; j++ ){
      if ( i == 1 && j == 1 ){
        L[i][j] = m[1][1];
        P[i][j] = '-';
      }
      else{
        if ( L[i-1][j] < L[i][j-1] ){
          L[i][j] = m[i][j] + L[i-1][j];
          P[i][j] = '←';
        }
        else{
          L[i][j] = m[i][j] + L[i][j-1];
          P[i][j] = '↑';
        }
      }
    }
  }
  return L[n][n];
}

출력

void printPath(){
  int i =n, j = n;
  while ( P[i][j] != '-' ) {
    print( i + " " + j );
    if ( P[i][j] == '←' )
      j = j-1;
    else
      i = i-1;
  }
  print( i + " " + j );
}
void printPathRecursive(int i, int j){
  int i = n, j = n;
  if( P[i][j] == '-' )
    print( i + " " + j );
  else{
    if ( P[i][j] == '←' )
      printPathRecursive( i, j - 1 );
    else
      printPathRecursive( i - 1 , j );
    print( i + " " + j );
  }
}
Dynamic Programming(1)

인프런의 영리한 프로그래밍을 위한 알고리즘 강좌를 보고 작성한 문서입니다.


Fibonacci Numbers

int fib(int n){
  if (n==1 || n==2)
    return 1;
  else
    return fib(n-2) + fib(n-1);
}

fibonacci numbers

Memoization

int fib(int n){
  if (n==1 || n==2)
    return 1;
  else if (f[n] > -1)   // 배열 f가 -1으로 초기화되어 있다고 가정
    return f[n];        // 즉 이미 계산된 값이라느 ㄴ의미
  else {
      f[n] = fib(n-2) + fib(n-1); //중간 계산 결과를 caching
      return f[n];
  }
}

memoization

Bottom-up

int fib(int n){
  f[1] = f[2] = 1;
  for( int i=3; i<=n; i++)
    f[n] = f[n-1] + f[n-2];
  return f[n];
}

bottom-up


이항 계수(Binomial Coefficient)

이항계수

int binomial(int n, int k)
{
  if ( n == k || k == 0)
    return 1;
  else
    return binomial(n - 1, k) + binomial(n - 1, k - 1);
}

Memoization

int binomial(int n, int k){
  if ( n == k || k == 0)
    return 1;
  else if (binom[n][k] > -1)  //배열 binom이 -1로 초기화되어 있다고 가정  
    return binom[n][k];
  else {
    binom[n][k] = binomial(n-1, k) + binomial(n-1, k-1);
    return binom[n][k];
  }
}

memoization_binomial

Bottom-up

int binomial(int n, int k){
  for (int i=0; i<=n; i++){
    for (int j=0; j<=k && j<=i; j++){
      if (k==0 || n==k)
        binom[i][j] = 1;
      else
        binom[i][j] = binom[i-1][j-1] + binom[i-1][j];
    }
  }
  return binom[n][k];
}

memoization_bottom

bottom-up 방식은 밑에부터 시작 한다는 뜻이 아니라 기본적인 부분부터 시작 한다는 것


Memoization vs. Dynamic Programming

  • 순환식의 값을 계산하는 기법들이다.

  • 둘 다 동적계획법의 일종으로 보기도 한다.

  • Memoization은 top-down방식이며, 실제로 피룡한 subproblem만을 푼다.

  • 동적계획법은 bottom-up 방식이며, recursion에 수반되는 overhead가 없다.

05일 한일

- 포트폴리오 작성 완료

  • 지속적인 리팩토링 필요

- 인프런의 알고리즘강의 Dynamic Programming

05일 느낀점

  • 포트폴리오 내용을 완성 하긴 했는데 아직 무엇을 어떻게 보여줘야 할지 나만의 색을 못 찾은 느낌이다. 그래서 그 부분을 고민하다 지속적으로 완성을 못했는데 아무래도 이건 아니다 싶어서 일단 완성을 해봤다. 일단 완성은 됐으니 틈틈이 수정해 나가야겠다.

    거기다 자소서 항목을 좀 더 추가하고 싶었는데 포트폴리오 작업을 하다 손을 못 대고 있었던지라 그 부분도 생각 날 때마다 조금씩 작성해야겠다.

  • Dynamic Programming, 즉 DP에 대해서 대략적인 내용들만 주변에서 듣고 처음으로 공부를 해 봤는데 역시나 핵심은 순환식을 구하는 부분이지 않을까 싶다. 이전에도 자료구조는 일부 공부해봤으나 알고리즘을 제대로 집중해서 공부해 본 적은 없어서 아직 문제를 푸는 속도가 빠르진 못한 것 같다.


06일 한일

- 마우스 a/s를 위해 용산 방문

- 우형신입 사원 몇분과 저녁 시간

06일 느낀점

  • 사실 현재 코드스쿼드 내에 신입사원 교육을 받으시는 분들과 크게 교류를 안 했던 감이 있었는데 오늘 많은 얘기를 하고 가까워진 것 같다.

07일 한일

- 자소서 완료

  • 좀 더 추가하고 싶은 내용들이 있지만 보류

07일 느낀점

  • 자소서가 일단 완료 되었다. 엄밀히 따지면 월요일에 완료 되었고 미세한 수정만 가했다. 수정이 아니라 추가 하고 싶은 내용이 몇 개 있지만 자소서만 수정하다 보면 다른 것들을 집중 하지 못하는 관계로 일단 보류 해야 겠다. 뭔가 초록불을 띄운 느낌이다.

  • 오늘은 코드스쿼드 내부 행사로 인해 밤코(밤에 코딩하기)를 못 하고 집에 왔는데 의도치 않게 생긴 이런 날 오랜만에 저녁에 쉬는 시간을 가질까 하고 누워있다가 아무래도 찝찝해서 다시 일어났다. 솔직히 최근 자소서를 쓰느라 공부는 제대로 집중 못한 부분이 있다. 내용 추가를 보류한 이유도 그 이유 때문이다. 내 공부에 소홀해지고 있다고 느낀다. 공부하는 것에도 흐름이 있다고 생각하는데 그 흐름이 깨지고 있다고 느껴진다. 굉장히 위험한 적신호라고 생각된다. 내일부터 정신 차리고 제대로 하자고 할 수도 있겠지만 내일 더 잘 집중하려면 아무래도 오늘 좀 봐야겠다. 너무 자신을 지나치게 몰아붙이는 것도 위험하지만 나아진 상황에 안주하려고 하지 말자. 후.. 오랜만에 솔직한 고백도 했으니 부끄럽지 않게 실천을 해야겠다.


08일 한일

- 인프런의 알고리즘강의 Dynamic Programming

- 백준 사이트의 문자열 문제 풀기

08일 느낀점

  • DP에 대한 개념을 최근 들어 공부해봤는데 결국 핵심은 점화식을 작성하는 것인 거 같은데 그게 힘든 것 같다. 강의를 다 보고 DP 문제를 풀려고 도전하다가 알고리즘 문제는 너무 오랜만이라 그런지 문제를 대하는 것 자체가 익숙지 않은 기분이 들어서 계획을 바꿔서 일단은 간단한 문제들을 접하면서 “알고리즘 문제” 자체에 익숙 해지기로 했다. 그래서 일단은 정말 간단한 형태의 문자열 문제를 풀어봤는데 예전과 달리 단위 테스트를 활용하니까 여러 가지 편리한 점을 느꼈다.

    몇 가지 있겠지만 대표적으로 문제를 해결할 메서드인 solution이 입출력 부분을 빼고 매개변수, 반환 값으로만 동작을 하니 확실히 문제를 해결하는 로직에만 집중을 할 수 있었고, 아주 당연하게도 자동화 테스트의 장점인 수동 테스트를 안 해도 된다는 점이었다. 전자 덕분에 이전에는 불편하다고 생각했고 한두 번씩 실수하던 백준 사이트의 입출력 방식에 구애되지 않고 정말 문제에만 집중해서 할 수 있어서 즐겁게 코딩 한 것 같다. 그렇긴 한데 일단 접근 자체를 잘못한 내 탓이지만 가능하면 스트림을 이용하려다가 문제를 비효율적으로 푼 부분이 발생 한 것 같다.


09일 한일

- 백준 사이트의 DP 문제

09일 느낀점

  • 자료구조는 공부해봤어도 알고리즘은 본격적으로 공부해 본 적이 없다가 최근 들어 DP를 공부해 보고 있었는데 예전 9월쯤 못 풀었던 문제를 어떻게 접근해야 했었는지 알 것 같다. 일단 접근법들을 익히기도 하느라 직접 문제만 보고 푼 건 간단한 몇 문제밖엔 없지만 엄청 어려운 문제만 아니라면 일단은 이전처럼 막막함을 느낄 것 같지는 않다.

    한동안 코딩을 안 하다가 해서 재미를 느끼고 있긴 하지만 알고리즘 공부는 일정 이상부터는 학습하는 즐거움보다는 토익 공부와 비슷하게 풀이법에 집중되는 것 같아서 학습하는 재미는 떨어지는 것 같다.


10일 한일

- 읽을만한 내용이 꽤 있어 보이는 블로그 발견

  • http://springmvc.egloos.com/

  • 오랫동안 업데이트가 안되고 있지만 해당 블로그의 주인도 학습을 하며 작성 한 글들이고 대부분 학습을 해본 내용에다 작성자의 생각도 엿볼 수 있는 것 같아서 좋을 것 같다.

- 가상 메모리 (출처: 컴퓨터 시스템, 저자: Randal E. Bryan)

  • 가상 메모리는 세 개의 중요한 기능을 제공한다.

    1. 메인 메모리를 디스크에 저장된 주소공간에 대한 캐시로 취급해서 메인 메모리 내 활성화 영역만 유지하고, 데이터를 디스크와 메모리 간에 필요에 따라 전송하는 방법으로 메인 메모리를 효율적으로 사용한다.

    2. 각 프로세스에 통일된 주소공간을 제공함으로써 메모리 관리를 단순화한다.

    3. 각 프로세스의 주소공간을 다른 프로세스에 의한 손상으로부터 보호한다.

10일 느낀점

  • 가상 메모리를 캐시로 취급한다. 이 키워드가 이전까지 가지고 있던 가상 메모리에 대한 개념들을 하나로 이어지게 해준 것 같다. 덕분에 가상 메모리의 중요성 혹은 존재에 대해 좀 더 와닿게 됐다. 이전까진 메인 메모리의 부족한 공간을 보충하기 위한 존재란 것과 페이징 기법이 있다고만 단순히 학습하고 스스로 무언가 생각을 안 해 봤던 것 같다.

  • 컴퓨터 시스템이란 책이 당장 읽기에는 여유가 없긴 하지만 꽤나 재밌어 보이는데 언젠가는 구매해야겠다. 겸사 겸사 구매하고 싶은 도서 목록을 만들어 봐야겠다.


11일 한일

- 경기창조혁신센터에서 공부

  • 상코와 재민씨의 스터디 분들을 만남

- 기본적인 자바 내용 학습

  • 좋은 블로그 발견, 딱 내가 포스팅하고 싶었던 방식으로 글을 작성하신다.

11일 느낀점

  • 상코를 통해 새로운 분들을 만났는데 현재의 나와는 달리 직장을 다니시는 분들이다. 개발자를 바라보는 일반인들의 시선 중엔 지속적으로 자기 개발을 위해 공부를 하는 것에 대해 불쌍(?) 하다는 시선을 가진 사람들도 존재하는데(내 주변에도 있다) 개인마다 재미를 느끼는 부분은 다르겠지만 그런 학습 속에서 재미를 느끼는 부분이 있기 때문에 그게 가능 한 것 같다.(스스로 잘 하고 싶다는 원초적인 욕심도 포함될 수 있을 것 같다) 아직까지는 자기 개발을 위해 학습을 하는 분들 중에선 별수 없이 꾸역 꾸역 하는 분은 못 봤다. 이런 부분에 메리트를 느끼기도 해서 개발자의 길에 들어온 건데 앞으로도 쭉 유지됐으면 좋겠다. 특히 그런 분들이랑 대화를 하면 지식적으로 유익한 대화를 했는지를 떠나서 좋은 자극이 되는 것 같다.