728x90

 개정 6판까지 나와, 길벗출판사에서 개정판 서평 이벤트를 하게 되어 읽게 되었다.

 평소 유니티나 언리얼 엔진과 같은 게임 제작에 대해 관심이 있었으나, 게임 제작은 코드보다 제작 툴을 다룬다는 느낌이 커서 어려웠었다.

 큰 총 평은 이 책은 툴을 다르는데 있어 쉽긴 한데 부족하다였다. 왜냐하면 전반적인 내용들이 정말 쉽고 쉬운 설명들로 가득했다. 특히 2D로 만드는 룰렛판의 경우, 사용하는 언어와 툴은 다르지만 웹 사이트로 만드는 룰렛과 정말 비슷하고 똑같다는 생각이 들었다. 유니티를 처음 보는 데도 정말 쉽다.

 하지만 부족하다라는 생각이 잊혀지지가 않았다. 왜 부족할까? 먼저 이 책을 읽고 싶은 사람은 게임을 만들고 싶은 사람일 텐데 게임을 만드는 데 부족한 지식이 많다. 유니티 교과서라 유니티만 다루고, 게임 서버에 관한 내용이 없는 것인가란 생각이 들었다. 그래서 어떠한 측면에서는 반쪽짜리 책이라 말할 수 있지 않을까 싶다. 차라리 예제를 하나로 추리고, 기초를 다룬 후 서버를 붙이면서 하나의 완성된 게임을 만드는 방향이었다면 좋지 않았을까?

 그리고 책 중간 중간 개정판의 흔적이 보인다. 귀여운 캐릭터가 표지에서도 보이지만, 책 중간 중간 옛날 느낌의 그림들이 있었다. 그리고 얼핏하면 어려울 수 있는 수학적인 내용도 그림으로 쉽게 설명해준다는 점도 장점이다.

내용이 워낙 쉬워서 입문하기 좋다는 것과 너무 쉬워서 아쉽다는 말만 자꾸 생각나는 책인 것 같다.

반응형
728x90

문제는 간단하다. 아래 지문을 그대로 구현만 하면 된다.

LZW 압축은 다음 과정을 거친다.

  1. 길이가 1인 모든 단어를 포함하도록 사전을 초기화한다.
  2. 사전에서 현재 입력과 일치하는 가장 긴 문자열 w를 찾는다.
  3. w에 해당하는 사전의 색인 번호를 출력하고, 입력에서 w를 제거한다.
  4. 입력에서 처리되지 않은 다음 글자가 남아있다면(c), w+c에 해당하는 단어를 사전에 등록한다.
  5. 단계 2로 돌아간다.

단순히 쉽지만, C++ String을 쓰면서 깨달았던 점을 정리하기 위해 글을 작성한다.

1. Char to String

String으로 변환할 때 단순하게 암묵적 변환을 쓰면 해당 글자에 해당하는 아스키 코드의  숫자가 들어간다. 그리고 명시적으로 변환한다고 한들 제대로 되지 않으니, String 변수를 하나 선언해야 한다.

string s = ""; s += c; 와 같이 해도 되나 길어서 별로니 아래처럼 하면 된다.
string(1, c); 

2. 문자열을 찾을 때에는 문자열을 잘라 사용하기

찾는 위치를 정해주지 않기 때문에 편리하다.

 

#include <string>
#include <vector>
#include <map>
#include <iostream>
using namespace std;

vector<int> solution(string msg) {
    vector<int> answer;    
    map<string, int> m;
    
    for(int i=0; i<26; i++) {
        m.insert({std::string(1, 'A' + i), i+1});
    }
    
    bool check = true;
    int w = 0;
        
    while(msg.size() > 0) {
        int index = 0;
        string temp = msg.substr(w, 1);
        int i = msg.size();
        for(; i>0; i--) {
            temp = msg.substr(w, i);
            if(m.find(temp) != m.end()) {
                index = m[temp];
                break;
            }
        }
        temp = msg.substr(w, i+1);
        m.insert({temp, m.size()+1});
        answer.push_back(index);
        msg = msg.substr(i);
    }  
        
    return answer;
}

 

반응형
728x90

헷갈렸던 부분

1. 배열의 size 고려

  • 주어진 방향으로 이동할 때 공원을 벗어나는지 확인합니다.
    해당 조건을 고려하기 위해서는 배열의 크기를 제대로 인지하고 있어야 하는데 매번 문제 풀 때마다 이 부분이 악취가 나는 부분이었습니다.

size()는 배열이 3칸이면 2를 반환하는 것이 아닌 3을 반환한다.
즉 index는 0부터 시작함으로 우리가 조건을 확인할 때 -1을 하거나 =으로 확인해줘야 한다.

Q. 인지의 오류를 줄일 수 있는 방법은 없을까?

애초에 w와 h에 -1을 하고 =을 사용하면 조금 더 인지적 오류 없이 사용하기 좋을 것 같다는 생각이 드네요.

int h = park.size() - 1;
int w = park[0].size() - 1;

for(int i=0; i<= h; i++) {
        for(int j=0; j<= w; j++) {
            if(park[i][j] == 'S') {
                point = { i, j };
                goto start;
            }
        }
    }
for문은 배열의 내부를 돌기 때문에 =을 사용하고

if(point.first < 0 || point.first > h ||
               point.second < 0 || point.second > w) {
                point = temp;
                break;
 } 

범위를 나갔는지 확인할 때에는 >으로 확인하면 되니 인지적 오류가 줄어들 것 같습니다.

2. char to int, string to int 
매번 헷갈리나 char는 간단하게  - '0'을, string의 경우 stoi를 쓰면 된다.

중요하게 생각했던 점

1. 내 위치를 저장하는 구조
pair를 사용하여 한 묶음으로 가지고 다녔다.
배열을 사용해도 되나 인지적으로 0번이 세로고, 1이 가로임을 생각하는 것이 불편했다.
first와 second로 생각하면 현재 다루고 있는 배열과 다른 생각 묶음으로 가져갈 수있어서 구분이 쉽다고 생각했다.

2. 예외 조건 생각하기
동서남북으로 point를 변경하여 조건을 매번 확인해줘야 하는데 
동 서 남 북으로 반복문을 작성하고 확인하면 코드가 늘어나서 이동은 switch 문을 사용해서 코드의 양을 줄이도록 해봤다.

3. 레이블을 이용한 goto문
악마의 goto문이지만 이중 for문을 나갈 때 유용해서 애용해보려고 써봤습니다. 좋네요.
기존에는 bool check사용해서 한번 더 if문을 확인했는데 이중 for문에 한해서는 사용하는 것이 코드도 깔끔하고 좋네요.

#include <string>
#include <vector>
#include <iostream>
using namespace std;

// 로봇 강아지에 미리 입력된 명령에 따라 진행하며, 명령은 다음과 같은 형식으로 주어집니다.
// ["방향 거리", "방향 거리" … ]
// "E 5"는 로봇 강아지가 현재 위치에서 동쪽으로 5칸 이동했다는 의미입니다.
// 명령 수행하기 전에 확인
// 👾 주어진 방향으로 이동할 때 공원을 벗어나는지 확인합니다.
// 👾 주어진 방향으로 이동 중 장애물을 만나는지 확인합니다.
// 위 두 가지중 어느 하나라도 해당된다면, 로봇 강아지는 해당 명령을 무시하고 다음 명령을 수행합니다.
vector<int> solution(vector<string> park, vector<string> routes) {
    vector<int> answer;
    // 배열로 관리하는 방법도 있을 것 같음
    pair<int, int> point = { 0, 0 };
    
    int h = park.size();
    int w = park[0].length();
    // 배열의 크기가 3칸임으로 3칸이 나온다. 2칸이 아니라, index는 0부터 시작임으로 =으로 해야함.
    for(int i=0; i<h; i++) {
        for(int j=0; j<w; j++) {
            if(park[i][j] == 'S') {
                point = { i, j };
                goto start;
            }
        }
    }
    
start:
    for(string route: routes) {
        pair<int, int> temp = point;
        const char op = route[0];
        // char를 int로 변경하는 방법으로 - '0'이 있고, (int) <- 앤 잘 안 됨
        const int n = route[2] - '0';
        // string to i => stoi
        // const int n = stoi(route[2]);
        
        for(int i=0; i<n; i++){
            switch(op) {
                case 'N': point.first -= 1; break;
                case 'S': point.first += 1; break;
                case 'W': point.second -= 1; break;
                case 'E': point.second += 1; break;
            }
            
            // 범위가 w-1, h-1이라 =을 해줬는데 이게 의식적으로 잘 안 그려짐
            if(point.first < 0 || point.first >= h ||
               point.second < 0 || point.second >= w) {
                point = temp;
                break;
            } else if(park[point.first][point.second] == 'X') {
                point = temp;
                break;
            }
        }
    }
    answer.push_back(point.first);
    answer.push_back(point.second);
    
    return answer;
}

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

 

프로그래머스

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

programmers.co.kr

다른 사람 코드를 보고 느낀 점

int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};

switch를 사용하지 않고 이동을 하는 방법
-> 그래프 문제 사용할 때 많이 쓰던 방법인데 여기서도 이렇게 사용할 수 있구나 싶기도 하고 오히려 복잡한 것 같기도 한 것 같다는 생각이 드네요.

    int cx, cy;
                tie(cx, cy) = {i, j};

변수로 선언한 것들을 tie로 묶어서 받는 모습, 굳이란 생각도 들지만 저런 방법이 있다는 게 신기하네요.

반응형
728x90

Insert한 데이터의 값을 바로 가져오고 싶을 때 유용한 문법이 있다.

selectKey다.

해당 문법을 사용하면 되는데 기존 값 방법과 반대 방향으로 흘러가 인지적 오류가 발생하는 것 같아 글을 쓴다.

보통 마이바티스로 값을 가져올 때 Select를 이용하고 해당 값을 resultType에 타입을 명시해서 값을 반환한다.

하지만 Insert 시 반환된 값은 정해져있다. 내가 Insert 한 데이터의 수다.

이미 반환하는 값이 있기에 selectKey를 쓴다고 반환되는 값이 바뀌지 않는다.

그럼 어떻게 받는가?

내가 parameter로 넘긴 데이터에 꽂혀 반환된다.


반응형
728x90

타임리프를 사용하다보면 자바 컨트롤러에서 던져주는 model 객체 내 값들을 JS에서 쓰고 싶은 욕구가 생긴다.

왜냐하면 Js에서 어떤 작업을 해야 하는데 필요한 값들이 페이지 내에 존재하는 경우가 많고, 이를 HTML 태그에서 갈무리를 해서 써야 한다. 그러면 document나 Jquery를 이용해서 HTML 내에 값을 가져와서 사용한다.

HTML 내 태그에 값을 숨겨 놓고 그것을 가져다 쓰는 작업이 꽤나 귀찮다. 

애초에 JS 변수 내에 값이 있으면 해결되는 문제가 아닐까?

그렇게 하는 방법이 있다.

package com.example.kg;

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

@Controller
public class KgController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("Hello", "hello");
        return "/kg";
    }
}

그냥 단순한 예제이니 model에 넣은 값만 확인하자.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
    <div th:text="${Hello}"></div>
</body>
<script th:inline="javascript" type="text/javascript">
    const hello = [[${Hello}]];
</script>
<script  type="text/javascript" th:src="@{/kg.js}"></script>
</html>

위처럼 inline에 넣어두면 된다.

여기서 th:inline을 사용하면 js 변수 내에 [[${ }]]로 가능하다.
이게 왜 좋냐면 th:inline을 사용하지 않으면 [[${}]] 양 옆에 /* */를 사용해야 한다.
그리고 외부 Js에서는 이렇게 사용할 수가 없다. 
그런 이유는 html을 렌더링? 서버에서 템플릿 작업을 해주면서 템플릿 언어에 해당하는 문법을 보고 작업을 해준다.

그렇기 때문에 html에 외부 js 파일이 내장되어 있지 않으니 정상적으로 변수에 값이 들어가지 않는 것이다.

그리고 th:inline이 없으면 템플릿 엔진이 이를 작업해주지 않는 것 같다.

? 그런데 /* */는 로 해주면 어떻게 읽어서 작업해주는건지 신기 하다.

중요한건 th:inline을 하고 /* */ 없이 JS에 값을 쓸 수 있다는 사실이다.

반응형
728x90

아토믹 코틀린 책이란

 코틀린의 철학을 이해하기 위한 서적이라 볼 수 있다. 그래서 코틀린스러운 방법에 대해서 각 방법을 다뤄 준다.

종합 평가

 파이썬을 할 때 파이써닉한 코드를, C언어는 씨스럽게 등 그런 말을 종종하곤 한다. 하지만 현실은 자바공화국인 한국에서는 자바만 아니 자바 8에 머물러있으니 지루하기 그지 없다. 이런 사막같은 곳에 오아시스란 코틀린이 아닐까 자바 대체 언어로 인식되며, 스프링 프레임워크를 이용할 수 있고 자바에서 불편했던 코드들을 코틀린으로 어느정도 대체하여 편리함을 추구할 수 있게 되었다.
 하지만 문제가 무엇일까? 현업에서 모두가 코틀린 문법을 제대로 숙지하여 코스럽게 코드를 작성할 수 있을까? 만약에 모두가 코스럽게 코드를 작성한다한들 다음 인계자가 코스럽게 코드를 작성할 수 있을까? 자바에서 코틀린으로 언어만 바뀐 코드를 작성하고는 있지 않을까? 이처럼 여러 문제들이 존재한다.
 그렇기 때문에 이 책을 읽으면서 모두가 너무 코틀린스럽지 않으면서 쉽게 배울 수 있는 개념정도로 예를 들어 data class와 같은 개념들을 주로 찾아보았다.
하지만 그런 쉽게 배울 수 있는 팁들과 함께 철학을 알아야 했다.
-> 친절하게도 책 앞부분에 언어 계보와 각 언어들에 대한 점을 착안하여 언어가 만들어 졌는지 알려주는 페이지가 존재한다.

철학

Non-NullAble

자바에서는 Null인 상태와 Null이 아닌 상태가 공존하기 때문에 NPE 발생 여부를 매번 확인해야 한다.
예를 들어 대게 DB에서 값을 읽어올 경우
if (list.size() > 0 ) 으로 예외 상황을 체크할 수 있지만, 모종의 이유로 list 객체가 생성되지 않았다면 Null Pointer Exception이 발생할 것이다. 실제로 그런 경험을 해본 적이 있다. 그래서 assert를 이용하여 if 시작 전에 null이 아닌 것을 확인하는 단언문을 설정한다.
그렇기 때문에 코드를 낭비한다. 그래서  코틀린이나 C# 과 같은 언어에서는 애초에 Non-Null 상태임을 기본으로 가져간다. 그리고 코틀린 문법으로 val a: String과 val b: String?은 전혀 다른 타입이란 점을 강조한 부분이 꽤나 좋았다.
이런 철학적인 부분을 이해하고 넘어가는 것이 중요하다고 생각했다.

Final은 Default요

자바로 코드를 작성하면서 final 키워드의 불편함을 느끼거나 왜 있는지에 대한 의문을 가져본 경험이 있을까?
나는 javascript나 C++에서 const를 이용하여 변경되지 않는 변수를 상수 처리하여 사용했다.
그런데 Java에서는 final 키워드를 붙이는 일이 꽤나 익숙치않았다.  다른 언어를 사용하면서 const는 느낌이 바로 오지만 final은 그렇지 않았기도 했고, 스프링 강의 영상을 보아도 쓰는 것을 거의 보지 못했기 때문이다. 남들이 안 하니 내가 이상한 느낌이다
하지만 코틀린에서는 기본적으로 val과 var를 제공하며 이에 따라 IDE에서 한번도 변경되지 않은 var 변수에는 val로 사용하라고 권유한다.
아래 내용과 이어지는 내용이지만 자바에서는 상속을 명시적으로 금지하기 위해서는 final 키워드를 키워드에 붙여야 한다. 하지만 코틀린은 그렇지 않다. 그런 이유는 무엇일까?
fragile base  class란 치약한 기반 클래스 문제가 있다. 기반 클래스가 변경에 의해 깨져버린 경우 하위 클래스가 모두 깨져버릴 수 있다. 그렇기 때문에 의도 파악이 중요하다. 그래서 final이 기본 설정으로 되어 있다.

Open 키워드

상속이 가능하다면 open이란 키워드를 의도적으로 붙이면서 상속이 가능하다는 것을 알려준다.
이런 식으로 다른 언어와의 다른 점을 찾아가며 기존에 쓰고 있는 언어에 대한 이해도와 다른 언어에 대한 이해도가 같이 올라갈 수 있다.

식이란 개념덕분에 코드에 상상력이 더 해지거나 코드가 간결해진다.

public int hello() {
if(true) {
    	return 1;
    } else {
    	return 2;
    }   
}
fun hello(): int {
	return if(true) {
    	1 
    }
    else {
    	2
    }
}

개념

1. data class의 동등성 보장과 copy를 통한 새로운 객체 반환하기
- Spring 이 용시 자동으로 생성되는 HashCode를 이용하면 안 된다. <- 검색을 통해 알게 된 내용
2. 스프레드 연산자 *를 이용한 list를 이용한 가변 인자 보내기
- DB에 여러 데이터를 저장할 때 꽤 유용해보였다. 왜냐하면 들어올 변수의 수를 정하지 않기 때문이다.
3. 람다를 val과 var에 담을 수 있다.
4.레이블
5.확장 프로퍼티

깨닫게 된 개념

다중 인터페이스 상속과 다중 상속

개체지향 개념을 학습하고, 직접 코드를 작성하다 보면은 인터페이스와 추상 클래스, 상속에 대한 혼동이 왔었다.
다 비슷하고 조금씩만 다르기 때문에 A란 것을 하기 위해 인터페이스를 써도 되고, 추상 클래스를 써도 되고 그렇게 느껴진다. 그래서 제일 먼저 하는 행동이 뭘까? 고민이 아니라 검색이다.
검색을 통해 인터페이스와 추상 클래스의 차이점?을 검색한다.
C++은 왜 다중 상속을 지원하고, Java나 Kotlin은 인터페이스 상속을 지원할까?
책에서도 설명했듯이 상태 변수를 관리하기 힘든 문제로 인터페이스 상속만 지원한다.
C++에 대한 다중 상속에 대해 설명이 없었지만 내가 정리한다면 C++은 워낙 제공하는 기능이 많아 써야하는 것과 쓰지 말아야하는 것을 구분할 줄 알아야 한다는 말이 있을 정도이다. 그렇기 때문에 다중 상속을 지원해주며 예외적인 상황에서 유용하게 사용할 수 있으나 그런 상황이 아니라면 지양해야하는 문법이다.

좋았단 점

1. 언어에 대한 설명만 있는 것이 아닌 철학과 개념에 대한 설명이 곁들어져 있다는 점
2. 코드 설명 시 테스트으로 코드를 설명해준다는 것
3. 각 챕터가 끝나면 메모 페이지가 존재한다는 점
4. 아는  기호나 기술에 대한 키워드에 대해 영어로 한번 더 알 수 있어서 좋았다.

아쉬웠던 점

1. 코틀린이라면 경량 스레드인 Coroutine이 필수가 아닐까?
2. DSL 과 관련된 내용이 없다는 점
3. 책 본문에서 연습 문제를 언급하지 않는다는 점
4. 아토믹 코틀린이란 책 제목때문에 테스트로 쓰이는 아토믹 라이브러리와 혼동이 왔었다.

책을 추천하는 이유

 코틀린에 대한 지식을 학습한다면 꽤 좋은 책이며, 책 표지도 이쁘니 소장할만한 가치가 있다. 코틀린 인 액션과 같은 책보다 훨씬 좋다고 생각한다. 코틀린 인 액션의 경우, 너무 글만 적혀있으니 언어를 배우는 것인지 생각이 든 경우가 많았다. 하지만 아토믹 코틀린을 이용하여 빠르게 코틀린을 습득한 후 어느정도 익숙해진 뒤  코틀린 인 액션을 통해 깊은 내용을 읽어보면 좋을 것 같다.
하지만 코루틴에 대한 내용이 없기 때문에 안드로이드 개발하는 분들께는 이 책 한 권으로 만족할 수 없을 것 같다. 물론 언어 자체만 배우는 저서임으로 안드로이드 관련 내용을 따로 봐야하긴 하지만...
 

반응형
728x90

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

 

프로그래머스

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

programmers.co.kr

10일동안 원하는 제품을 모두 할인해서 구매해야한다.

unoredred_map의 사용법이 신기했던 문제다.

#include <string>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <deque>
using namespace std;

int solution(vector<string> want, vector<int> number, vector<string> discount) {
    int answer = 0;
    
    deque<string> dq;
    unordered_map<string, int> m;
    unordered_map<string, int> origin;    
     
    for(int i=0; i<want.size(); i++) {
        origin.insert({want[i], number[i]});
    }
    
    for(int i=0; i<10; i++) {
        string temp = discount[i];
        dq.push_back(temp);
        m[temp]++;
    }
        
    if(m == origin) {
        answer++;
    }

    for(int i=10; i<discount.size(); i++) {
        m[dq.front()]--;
        dq.pop_front();
        m[discount[i]]++;
        dq.push_back(discount[i]);
        
        bool check = true;
        for(int j=0; j<want.size(); j++) {
            
            if(m[want[j]] < origin[want[j]]) {
                check = false;
                break;
            }
        }
        if(check) {
            answer++;
        }
    }
        
    return answer;
}

맵에 있는 값을 --처리하여 0이 되어도, map에서 사라지지 않는다.

그렇기 때문에 비교하는 for문이 하나 더 들어가게 된다.

erase를 써서 지워버린다면, for문이 사라지고 == 처리로 비교가 한번에 가능해진다.

하지만 내가 사고 싶은 물건보다 더 많이 할인하는 경우를 체크하지 못하지 않을까 싶지만 사실 상관없다.
=>  애매한 부분이라 정확한 설명은 못하지만, ==처리로 같은지 확인할 수있다는 점을 알게 되었다는 점에서 신기하다.

#include <string>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <deque>
using namespace std;

int solution(vector<string> want, vector<int> number, vector<string> discount) {
    int answer = 0;
    
    deque<string> dq;
    unordered_map<string, int> m;
    unordered_map<string, int> origin;    
     
    for(int i=0; i<want.size(); i++) {
        origin.insert({want[i], number[i]});
    }
    
    for(int i=0; i<10; i++) {
        string temp = discount[i];
        dq.push_back(temp);
        m[temp]++;
    }
        
    if(m == origin) {
        answer++;
    }

    for(int i=10; i<discount.size(); i++) {
        m[dq.front()]--;
        if(m[dq.front()] == 0) {
            m.erase(dq.front());
        }
        dq.pop_front();
        m[discount[i]]++;
        dq.push_back(discount[i]);
        
        if(m == origin) {
            answer++;
        }
    }
        
    return answer;
}
반응형
728x90

https://koolreview.tistory.com/127

 

Spring Boot Security 5 - Oauth2.0 구글 로그인 (HTTP BASIC 탐방) - 1

단순하게 Spring Boot Security 5에 대해 코드만 작성하는 글이 아니라, 이해를 위한 글을 작성하기 노력하고 있습니다. Oauth2.0을 알아보기 전에 기본인 HTTP Basic 로그인에 대해 알아보겠습니다. 1. 종속

koolreview.tistory.com

이전 글에서 HTTP BASIC 로그인에 대해 알아보았습니다. 이번에는 HTTP OAUTH2.0로그인에 대해 알아보겠습니다.

1. 라이브러리 추가

implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-client', version: '2.7.1'

 

반응형

+ Recent posts