Sad Puppy 3 개발자 아지트 :: 개발자 아지트

쿼리 스트링(Query String)이란?

:웹 주소(URL)의 일부로, 클라이언트에서 특정 페이지나 기능을 요청할 때 추가적인 정보를 서버에 전달하기 위해 사용된다.

URL의 끝에 붙으며, ?로 시작하고 여러 개의 키-값 쌍으로 이루어져 있음.

 

쿼리 스트링이 필요한 상황 예시

: 인터넷 쇼핑몰에서 장난감을 검색하려한다. 이때 필요한 쇼핑몰 웹사이트의 주소는 http://www.toyshop.com이다.

검색어를 입력하고 검색 버튼을 누르면, 웹사이트는 클라이언트가 어떤 장난감을 검색했는지 서버에 알려줘야 한다.

이때 쿼리 스트링이 사용된다. 

 

쿼리 스트링이 사용된 예시 URL

http://www.toyshop.com/search?product=lego&color=red

 

URL 요소별 설명

  • http://www.toyshop.com/search: 기본 URL (검색 페이지)
  • ?: 쿼리 스트링의 시작을 알림
  • product=lego: 첫 번째 키-값 쌍. 키는 product 값은 lego
  • &: 여러 개의 키-값 쌍을 구분하는 구분자
  • color=red: 두 번째 키-값 쌍, 키는 color 값은 red

즉, 이 URL은 "lego"라는 제품과 "red"라는 색상을 검색하겠다는 정보를 서버에 전달한다. 

 

쿼리 스트링은 URL에 포함되어 서버에 전달되며, 서버는 이 정보를 사용해 요청한 데이터를 반환한다. 

이렇게 하면 사용자가 웹사이트에서 원하는 것을 쉽게 찾을 수 있다. 

 

쉽게 기억하는 방법

:쿼리 스트링은 마치 웹 주소에 붙이는 "메모"와 같다. 주소 자체가 어디로 갈지 알려주고, 쿼리 스트링은 그 주소에서 무슨 일을 해야 하는지 더 자세히 알려주는 것이라고 생각하면 편하다. 

  • 기본 주소: 집 주소 (어디로 갈지)
  • 쿼리 스트링: 방문할 때 가져갈 목록 (무엇을 할지)

 

Static resource

: 서버 처리 필요 없이 바로 클라이언트로 응답하는 처리 방식

 

특징

  •  특정 URL로 요청이 오면 static resource로 인식하고 바로 응답 수행 필요

 

(반대개념)

Dynamic resource

: 요청을 보내면 서버측까지 도달해서 필요한 메소드를 호출하고 리턴하는 방식

 

Spring에서는 Static resource와 Dynamic resource를 분리해, static resource 응답을 빠르게 해줄 수 있도록 지원한다. 


요구사항 1. /hello 요청 시 resources/templates/static.html 페이지가 응답할 수 있도록 설정

=> localhost:8080/hello 요청이 들어오면 내부에서 resources/templates/static.html 정적 파일을 보여줄 수 있도록 설정

 

요구사항 2. 쿼리 파라미터로 name 요청이 들어왔을 때 해당 값을 hello.html에서 사용할 수 있도록 하기

=> 쿼리 파라미터로 name 요청이 들어왔을 때, 해당 값을 hello.html에서 보여주기 

 

1. 컨트롤러 메서드 작성

2. 쿼리 파라미터로 전달된 값을 'hello.html'템플릿에서 사용할 수 있도록 하기 

 

 

[@GetMapping]

: Spring Framework에서 제공하는 어노테이션

HTTP GET 요청을 처리하기 위해 사용된다. 이 어노테이션을 사용해, 특정 URL 경로에 대한 GET 요청을 특정 메서드에 매핑할 수 있다. 

이는 주로 RESTful 웹 서비스에서 데이터를 조회하는 용도로 사용된다. 

*매핑(mapping): 특정 URL 요청을 특정 메서드에 연결하는 것을 의미한다. 

 

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {
	
    @GetMapping("/hello")
    public String sayHello() {
    	return "Hello, World!";
    }
}

 

@GetMapping("/hello")는 "/hello" 경로로 들어오는 GET 요청을 'sayHello' 메서드에 매핑한다. 

따라서 사용자가 브라우저에서 'http://localhost:8080/hello'로 접근하면, "Hello, World!"라는 문자열을 응답으로 받게 된다. 

 

(@GetMapping은 @RequestMapping 어노테이션의 축약형으로, 아래와 같은 방식으로도 구현이 가능하다. )

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController 
public class MyController {

	@RequestMapping(value = "/hello", method = ReqeustMethod.GET)
    public String sayHello() {
    	return "Hello, World!";
    }
}

=> @GetMapping은 코드의 가독성을 높이고 RESTful 서비스 개발을 편하게 만들어 준다. 

 

@RestController는 이 클래스가 RESTful 컨트롤러 임을 나타낸다. 

 

그러면 RestController 라우터는 아닌거지?=> yes

: @RestController Spring Framework에서 제공하는 어노테이션으로, 해당 클래스가 RESTful 서비스의 엔드포인트를 정의하고 있다는 것을 나타냄 

 

그러나, 이것 자체는 라우터는 아님.

그냥 라우팅 설정역할을 하는 메서드와 함께 사용될 뿐임

 

Spring에서 라우팅을 담당하는 부분

:@RequestMapping

@GetMapping

@PostMapping 등의 어노테이션

위 어노테이션들이 HTTP 요청을 특정 메서드에 매핑함. 

 

@RestController는 위 어노테이션을 포함하는 클래스이고, RESTful API를 제공한다는 의미를 가짐. 

 


Membercontroller.class

package cholog;

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

@Controller
public class MemberController {
    @GetMapping("/hello")
    public String world(@RequestParam (name = "name", required = false, defaultValue = "World") String name, Model model) {
        // TODO: /hello 요청 시 resources/templates/static.html 페이지가 응답할 수 있도록 설정하세요.
        // TODO: 쿼리 파라미터로 name 요청이 들어왔을 때 해당 값을 hello.html에서 사용할 수 있도록 하세요.
        model.addAttribute("name", name);
        return "static";
    }

    public Person json() {
        // TODO: /json 요청 시 {"name": "brown", "age": 20} 데이터를 응답할 수 있도록 설정하세요.
        return null;
    }
}

 

그동안 나혼자 이상한거하고 있었는듯; main에서 가이드 안보고 코드부터 뜯을 생각(MemberController부터 할 생각)했음

                                                                                                                                                                                                             

                                                                                                                                                                                                             

다시 cholog  https://cho-log.github.io/docs/spring/spring-mvc-1  이 페이지 보고 다시 하는중 

스프링 시작하기

1. Welcome Page

스프링 부트는 정적 페이지와 템플릿 시작 페이지를 모두 지원한다.

먼저 구성된 정적 콘텐츠 위치(main/resources/static/)에서 index.html 파일을 찾습니다. 하나라도 없으면 index 템플릿 (main/resources/templates/)을 찾는다. 둘 중 하나라도 찾으면 자동으로 응용 프로그램 시작 페이지로 사용된다.

 

처리 방법: 일단 main에 있는 resources/static/hi.html 의 이름을 index.html로 바꾸고 실행하니까 테스트 통과함

 

2 Static Page

resources/static 아래의 경로에 위치한 파일은 접근이 가능하다.  서비스에서 필요한 정적 자원들을 해당 경로에 위치시킨 후 활용할 수 있다. 

static과 templates폴더의 차이는?

:static 디렉토리와 tmeplates 디렉토리는 서로 다른 목적을 가진 파일들을 저장한다.

 

static 디렉토리

목적: 정적 리소스를 저장한다.

내용: css, javaScript, image, font 등과 같은 정적 파일.

기본 경로: src/main/resources/static

접근 방법: 브라우저에서 '/static' URL 경로를 생략하고, 정적 리소스의 경로를 직접 사용한다. 

ex) src/main/resources/static/image/logo.png => http://localhost:8080/images/logo.png 로 접근할 수 있다. 

 

templates 디렉토리

목적: 동적 템플릿 파일을 저장한다. 

내용물: Thymeleaf, FreeMarker, JSP 등과 같은 템플릿 파일.

기본 경로: src/main/resources/templates

접근 방법: 컨트롤러를 통해 템플릿을 렌더링하고 클라이언트에게 HTML 페이지로 제공함. 

ex) src/main/resources/templates/hello.html 파일은 컨트롤러에서 return "hello"; 와 같이 반환하면 브라우저에 렌더링 된다. 

 

 

처리 방법: 일단 main에 있는 resources/templates/static.html을 resources/static/static.html 으로 위치 변경함 

 

 

3. Template Engine

동적으로 페이지 처리를 하기 위해서는 템플릿 엔진을 활용할 수 있다. 이번에는 Thymeleaf를 활용해 요청에 대한 동적 처리를 한다.

쿼리 스트링(?name=brown)으로 전달된 name 값을 @RequestParam을 활용하여 컨트롤러 메서드의 파라미터로 주입받는다.

컨트롤러 메서드 내에서 뷰로 값을 전달하기 위해서 Model 객체를 활용한다.

Model 객체는 컨트롤러 메서드의 파라미터로 주입 받을 수 있고, addAttribute 메서드를 통해 값을 전달할 수 있다. 

 

 

12번째줄 hello.html인가? 잘못된거 아닌가?
여기서는 /hello 요청시 hello.html페이지가 응답하도록 하라고 함..

 

처리 방법: 일단 main에 있는 MemberController 코드 수정하고 name 코드가 있는 경우와 없는 경우 조건문으로 반환값 다르게 작성해줌. 단, 쿼리 파라미터가 없는 경우에는 static을 반환해야한다고 기존 코드 가이드라인 주석에 적혀있었는데, static.html은 static폴더에만 존재했는데 아마 자동으로 static폴더에서 static.html을 반환하지 않았나 싶음.. 맞는지 확인 필요함 

 

 

4. Json 응답

컨트롤러 메서드(웹 요청을 처리하는 함수)의 리턴타입을 그대로 body에 담아 응답하기 위해서는 @ResponseBody를 활용할 수 있음 

해당 어노테이션을 사용하면, 컨트롤러 메서드가 반환하는 데이터를 그대로 웹 응답의 본문(body)에 담아 보낼 수 있음 

 

해당 상황은 다음과 같이 비유하여 설명할 수 있음.

: 친구에게 편지를 보내는 상황이고, 편지지에 글을 써서 편지 봉투에 넣어 보낼 수 있는 상황임.

그러나 친구가 바로 편지가 쓰여진 편지지을 받고 싶어 한다면, 편지 봉투 없이 바로 편지만 보낼 수있음

 

  • 편지지에 글을 쓰고 편지 봉투에 넣는 것 => 컨트롤러 메서드가 HTML 페이지를 반환하는 것을 의미함
  • 편지지를 바로 보내는 것=> 컨트롤러 메서드가 데이터를 그대로 웹 요청의 응답으로 보내는 것이고, 이때 @ResponseBody를 사용

처리 방법: 바로 return 값에 데이터를 넣어 보내면됨 

 

 

 

 

참고

 

*쿼리 스트링: https://dayae-dev.tistory.com/356

 

따로 궁금했던점

  • 자바는 왜 클래스에서 한줄 띄우고 시작하는지?
  • => 가독성을 높이기 위한 관습
  • => 컨벤션(많은 Java 코딩 스타일 가이드와 컨벤션에서 클래스 선언부와 클래스 몸체 사이에 한 줄을 띄우는 것을 권장함)

https://www.acmicpc.net/problem/20440

 

 

 

문제 해결 방법

 

1. 시간을 key로 모기수를 value로 한 딕셔너리를 만든다. 

2. key를 통해 딕셔너리를 정렬한 후, 순회하면서 현재 들어온 모기 수랑 그동안 순회하며 들어온 최대 모기 수를 갱신한다. 최대 모기 수를 갱신하는 중에는 flag변수를 통해 계속 인지하고있는다. 또한 최대 모기 수를 갱신하기 시작한 시간을 표시해준다. 

3. 최대 모기 수를 유지하다가 모기가 나가기 시작하면 최대 모기 수는 깨진다. 따라서 이때 flag변수를 통해 다시 이를 인지할 수 있도록 값을 바꿔주고, 그동안 최대 모기 수를 유지했던 시간을 표시해준다. 

4. 출력

 

코드 구현

시간 초과된 코드

더보기
import sys

n=int(sys.stdin.readline())
total=[]
minn=2100000000
maxx=0

for i in range(n):
    # 입장시간, 퇴장시간
    e, x = map(int, sys.stdin.readline().split()) 
    #print(e, x)
    if e<minn:
        minn=e
    
    if x>maxx:
        maxx=x

    total.append([e,x-1])

#print(minn, maxx)

total 

tmp=[]
for i in range(maxx):
    tmp.append(0)

for i in total:
    s, e = i
    for i in range(s, e+1):
        tmp[i]+=1

#print(tmp)

check = 0
start = -1
end = -1

result = []

cnt=0

for i, value in enumerate(tmp):
    if value == max(tmp):
        if start != -1:
            check=1
            continue
        else:
            #최초로 넣을때 
            check=1
            start = i
            result.append(i)
            cnt+=1
            start=-1
            

    else:
        if check==1:
            check=0
            end=i
            result.append(i)
            end = -1

#print(result)

print(max(tmp))


origin = result[0]
originEnd = 0
originStart=0
for i, value in enumerate(result):
    if i ==0:
        originStart=value
        continue

    if origin +1 == value:
        origin+=1
    else:
        originEnd=result[i-1]
        break

if originEnd==0:
    originEnd=result[-1]

    
print(originStart, originEnd)

정답 코드

 

(주석 있는 버전)

import sys
from collections import defaultdict
input = sys.stdin.readline

n = int(input())
d = defaultdict(int)
print('defaultdict', d)
for i in range(n):
    s, e = map(int, input().split())
    d[s] += 1
    d[e] -= 1




m, cnt = 0, 0 # cnt는 모기 마릿수 m은 최대갱신 모기 마릿수 
tem, txm = 0, 0# 최댓값 갱신시 시간과 갱신 해제시 시간
flag = False
for i in sorted(d.keys()):
    print(i)
    cnt += d[i]
    # 기존 최대 모기수 보다 현재 모기수가 더 클때
    # == 최고일때만 계산함 
    if cnt > m:
        m = cnt
        tem = i
        flag = True
    # 현재 모기수 cnt보다 최대 갱신 모기수가 더 크고
    # 만약, 현재 값이 모기가 나가는 타이밍일때
    # 현재 모기수에서 지금 시간의 모기를 빼면 음수값이라 더한 격이 되어
    # 지금 나간 모기를 더했을때의 값 = 최대값이 m인 경우이다. 
    # FLAG가 TRUE인 상태 
    # 마찬가지로 최고자리가 박탈 됐을때 계산함 
    elif cnt < m and cnt - d[i] == m and flag:
        txm = i
        flag = False
print(m)
print(tem, txm)

 

(주석 없는 버전) - 변수명 변경됨

import sys
from collections import defaultdict
input = sys.stdin.readline

n=int(input())
dict = defaultdict(int)

for i in range(n):
    # 입장시간, 퇴장시간
    e, x = map(int, input().split()) 

    dict[e] += 1
    dict[x] -= 1
    

maxx, nowMos = 0, 0
startTime, endTime = 0, 0
flag = False
for i in sorted(dict.keys()):
    nowMos += dict[i]

    if nowMos > maxx:
        maxx = nowMos
        startTime = i
        flag = True
    elif  nowMos < maxx and nowMos - dict[i] == maxx and flag:
        flag = False
        endTime = i

print(maxx)
print(startTime, endTime)

 

시간/공간 복잡도

 

입력값보다 입력값의 값의 범위가 워낙 커서 웬만하면 N으로 구현해야 했다. 

 

 

최적화 및 개선

 

  • 기존에는 입력값의 범위를 전부 순회하면서 최대 모기수를 체크해서 시간초과가 발생했었다. 
    • 이런 방식의 관점을 바꿨다. 
    • => 현재 방에 있는 모기의 수가 갱신이 되는 모기의 수일 경우에만 체크 

 

어려웠던 점

시간복잡도에 대한 개념은 알고있지만, 여전히 문제 풀기전에 시간복잡도에 대한 계산을 하는것이 어렵다. 

파이썬의 경우 1초에 최대 연산횟수가 logN인 알고리즘으로 계산해도 최대 연산 횟수가 10억번인데, 이 문제의 경우 값의 범위가 10억을 훌쩍넘어 20억쯤 된다. 이런 경우는 시간복잡도 계산을 어떻게 해야하는지 모르겠다. 

 

 

알게된 것

  • sys모듈의 sys.stdin.readLine() 함수를 input 변수로 저장 할 생각을 못하고 계속 수기로 작성해왔었는데 이제는 변수로 저장할 줄 알게됐다. 
  • 딕셔너리 자료구조가 이런경우에 유용함을 다시 상기시킬수 있었다. 

 

'코딩테스트 > 문제 풀이 - Python' 카테고리의 다른 글

[백준2110] 공유기 설치  (0) 2024.08.06
[백준16918] 봄버맨  (0) 2024.08.05
[백준18808] 스티커 붙이기  (0) 2024.08.01
[백준19637] IF문 좀 대신 써줘  (0) 2024.07.30
[백준12933] 오리  (0) 2024.07.24

https://www.acmicpc.net/problem/18808

 

 

링크 참고

 

문제 해결 방법

시간복잡도를 고려햐여 문제를 잘 읽고 그대로 구현하면 됨.

아래의 조건을 고려하여 코드를 작성하였음.

 

  • 노트북에 스티커를 붙이기전에 모눈종이의 크기만으로 노트북에 들어갈 수 있는지 확인한다. 
    • 모눈종이의 행이 노트북의 행의 크기를 넘거나 모눈종이의 열이 노트북의 열의 크기를 넘는 경우, 모눈종이의 행과 열을 뒤집었을때(90도 회전한다고 가정했을때)도 노트북의 범위를 벗어나는지 확인한다. 
      • 범위를 벗어나지 않는다면 90도 회전
      • 범위를 벗어난다면 다음 스티커로 넘어간다. 
  • 노트북에 모눈종이의 크기가 들어갈 수 있을때, 노트북의 빈곳에 스티커를 붙이는 시도를 한다. 
  • 노트북에 붙여진 스티커의 현황과 스티커는 겹쳐도 되지만, 붙이려는 스티커의 빈칸이 노트북에 붙여진 스티커 일부를 지워서는 안된다.(모눈종이 전체를 붙이려고 할 경우 주의)
  • 노트북에 스티커를 붙일때 회전을 먼저 해서는 안되고 일단 생긴 그대로 붙이려는 시도를 반드시 해야한다. 
    • 그 후 90도 회전을 3번 할 수 있다. (단, 90, 180, 270 순으로 진행해야함)
    • 3번의 회전이 끝나면 더이상의 회전은 의미가 없으므로, 스티커를 버린다.(다음 스티커로 넘어간다. )

 

코드 구현

 

시간초과난 코드 1

더보기
import sys, copy
# 스티커 각 칸은 상하좌우로 모두 연결되어있어야함 = 어디 끊기면 안됨
# 모눈종이에 스티커가 없는 불필요한 행이나 열이 있으면 정상아님

# 다른 스티커와 겹침X, 노트북 벗어나면X, 
# 노트북의 위쪽부터 채움(우선순위 = 1. 가장 위쪽의 위치
# , 위쪽에 여러곳일 경우2. 가장 왼쪽) 

# 만약 스티커 붙일 자리가 안보인다면, 스티커를 시계방향 90도 회전 후
# 붙일 자리 찾기

# 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 
# 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

# 다 붙인 후에 노트북에서 몇개의 칸이 채워졌는가?

#시간 복잡도
# 세로N(40) 가로M(40) 스티커의 개수K(100)

n, m, k = map(int, sys.stdin.readline().split())
stickerPocket = []
stickerPocketEntire=[]
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    tmp2=[]
    for j in range(r):
        tmp1 = list(map(int, sys.stdin.readline().split()))
        tmp2.append(tmp1)
    stickerPocket.append(tmp2)

print(stickerPocket)

#스티커는 받았던것 부터 차례로 붙임 (최대한 붙여야하고 이런거 없음)
#전체에서 1번을 먼저 붙임. 
# 단, 붙일 수 없는 경우는 판별해줌 
# 붙일 수 없는 경우
#   1. 노트북을 벗어나는경우[조건문]
#   2. 회전을 시도해본다 (3회전) -> 그래도 벗어나면 버림[시계방향90도로 넘겨주는 함수]
# 붙일 수 있는 경우
#   ->맨위, 맨왼쪽에 붙인다. =>[전체에서 스티커 붙여졌는지 확인하는 배열]
#   ->다음 스티커로 넘어간다. 

# 두번째 스티커를 붙일때 부터는 스티커를 붙일 수 없는 자리를
# 피해서 스티커를 붙여야 한다. 

visited=[ [0] * m for i in range(n)]

print('visited', visited)

def rotation(cnt, i):
    
    r, l = stickerPocketEntire[i]
    # 3번만 돌림 
    
    #90도로 돌리기 
    tmp = stickerPocket[i]
    nn=len(tmp) 
    mm=len(tmp[0])
    tmpList =[]
    for qq in range(mm):
        tmpList2=[]
        for pp in range(nn-1, -1, -1):
            tmpList2.append(tmp[pp][qq])
        tmpList.append(tmpList2)
    stickerPocket[i]=tmpList
    stickerPocketEntire[i]=[mm, nn]
    print('checkcnt: ', cnt)
    cnt+=1
    print('cnt: ', cnt)
    return cnt

def backtrack(p, q, i, r, l, visited):
    rem = copy.deepcopy(visited)
    print('초기 rem', rem)
    # 이부분 백트래킹으로 구현해야할듯 
    for j in range(r):
        for jj in range(l):
            #왼쪽 위부터 조회
            nr = p+j
            nl = q+jj
            if 0<=nr<n and 0<=nl<m:
                print('i:', i, "j:", j, "jj", jj)
                if stickerPocket[i][j][jj]==1:
                    #두번째 스티커를 자리에 붙여본다
                    if visited[p+j][q+jj]!=1:
                        #겹치는지 확인후 붙여보기
                        visited[p+j][q+jj]=1
                    else:
                        #겹친다.
                        #스티커를 붙이기 전으로 원래대로 돌아가기 
                        visited=copy.deepcopy(rem)
                        check = 0
                        print('리턴 visited', visited)
                        return visited, check
                check = 1
            else:
                print("뭐지 nr:", nr, "nl:", nl)
                visited=copy.deepcopy(rem)
                check = 0
                print('리턴 visited', visited)
                return visited, check
                
    if check == 1:
        return visited, check

i=0
cnt=0 # 회전 횟수
check=1
while(1):
    r, l = stickerPocketEntire[i]
    if (n<r or m<l) or check ==0: 
        # 모눈종이의 크기가 노트북을 넘어가면
        # 90도 회전 적용
        if cnt <=3:
            cnt = rotation(cnt, i)
            r, l = stickerPocketEntire[i]
        else:
            i+=1
            cnt=0
                
    if i == 0:
        #항상 0,0에서 부터 시작해야함
        for j in range(r):
            for jj in range(l):
                if stickerPocket[0][j][jj]==1:
                    visited[j][jj]=1
        i+=1
        cnt=0
    else:
        for p in range(n):
            for q in range(m):
                if i ==k:
                    break
                
                print('hi')
                print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                visited, check = backtrack(p, q, i, r, l, visited)
                print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                print('hi end')
                if check==1 or cnt>3: #스티커를 제대로 붙였을 때
                    i+=1
                    cnt=0

                if check==0 and cnt>3:
                    break
            if check==0 and cnt>3:
                break

    if i ==k:
        break

print("?????????")
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)

 

 

시간초과난 코드 2

더보기
import sys, copy
# 스티커 각 칸은 상하좌우로 모두 연결되어있어야함 = 어디 끊기면 안됨
# 모눈종이에 스티커가 없는 불필요한 행이나 열이 있으면 정상아님

# 다른 스티커와 겹침X, 노트북 벗어나면X, 
# 노트북의 위쪽부터 채움(우선순위 = 1. 가장 위쪽의 위치
# , 위쪽에 여러곳일 경우2. 가장 왼쪽) 

# 만약 스티커 붙일 자리가 안보인다면, 스티커를 시계방향 90도 회전 후
# 붙일 자리 찾기

# 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 
# 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

# 다 붙인 후에 노트북에서 몇개의 칸이 채워졌는가?

#시간 복잡도
# 세로N(40) 가로M(40) 스티커의 개수K(100)

n, m, k = map(int, sys.stdin.readline().split())
stickerPocket = []
stickerPocketEntire=[]
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    tmp2=[]
    for j in range(r):
        tmp1 = list(map(int, sys.stdin.readline().split()))
        tmp2.append(tmp1)
    stickerPocket.append(tmp2)

print(stickerPocket)

#스티커는 받았던것 부터 차례로 붙임 (최대한 붙여야하고 이런거 없음)
#전체에서 1번을 먼저 붙임. 
# 단, 붙일 수 없는 경우는 판별해줌 
# 붙일 수 없는 경우
#   1. 노트북을 벗어나는경우[조건문]
#   2. 회전을 시도해본다 (3회전) -> 그래도 벗어나면 버림[시계방향90도로 넘겨주는 함수]
# 붙일 수 있는 경우
#   ->맨위, 맨왼쪽에 붙인다. =>[전체에서 스티커 붙여졌는지 확인하는 배열]
#   ->다음 스티커로 넘어간다. 

# 두번째 스티커를 붙일때 부터는 스티커를 붙일 수 없는 자리를
# 피해서 스티커를 붙여야 한다. 

visited=[ [0] * m for i in range(n)]

print('visited', visited)

def rotation(cnt, i):
    
    r, l = stickerPocketEntire[i]
    # 3번만 돌림 
    
    #90도로 돌리기 
    tmp = stickerPocket[i]
    nn=len(tmp) 
    mm=len(tmp[0])
    tmpList =[]
    for qq in range(mm):
        tmpList2=[]
        for pp in range(nn-1, -1, -1):
            tmpList2.append(tmp[pp][qq])
        tmpList.append(tmpList2)
    print('before',stickerPocket[i])
    stickerPocket[i]=tmpList
    
    stickerPocketEntire[i]=[mm, nn]
    print('after',tmpList)
    print('checkcnt: ', cnt)
    cnt+=1
    print('cnt: ', cnt)
    return cnt

def backtrack(p, q, i, r, l, visited):
    rem = copy.deepcopy(visited)
    print('초기 rem', rem)
    # 이부분 백트래킹으로 구현해야할듯 
    for j in range(r):
        for jj in range(l):
            #왼쪽 위부터 조회
            nr = p+j
            nl = q+jj
            if 0<=nr<n and 0<=nl<m:
                print('i:', i, "j:", j, "jj", jj)
                if stickerPocket[i][j][jj]==1:
                    #두번째 스티커를 자리에 붙여본다
                    if visited[p+j][q+jj]!=1:
                        #겹치는지 확인후 붙여보기
                        visited[p+j][q+jj]=1
                    else:
                        #겹친다.
                        #스티커를 붙이기 전으로 원래대로 돌아가기 
                        visited=copy.deepcopy(rem)
                        check = 0
                        print('리턴 visited', visited)
                        return visited, check
                check = 1
            else:
                print("뭐지 nr:", nr, "nl:", nl)
                visited=copy.deepcopy(rem)
                check = 0
                print('리턴 visited', visited)
                return visited, check
                
    if check == 1:
        return visited, check

i=0
cnt=0 # 회전 횟수
check=1
while(1):
    r, l = stickerPocketEntire[i]
    if (n<r or m<l) or check ==0: 
        if l<=n and r<=m:
        # 모눈종이의 크기가 노트북을 넘어가면
        # 90도 회전 적용
        # 회전의 조건: 모눈종이가 노트북에 딱 들어맞지 않는경우(단, 가로 세로는 뒤집혔을때 노트북에 들어갈 수 있어야 함)
            if cnt <3:
                cnt = rotation(cnt, i)
                r, l = stickerPocketEntire[i]
            else:
                i+=1
                cnt=0
        else:
            i+=1
    
    if i == 0:
        #항상 0,0에서 부터 시작해야함
        for j in range(r):
            for jj in range(l):
                if stickerPocket[0][j][jj]==1:
                    visited[j][jj]=1
        i+=1
        cnt=0
    else:
        for p in range(n):
            for q in range(m):
                if i ==k:
                    break
                r, l = stickerPocketEntire[i]
                print('hi')
                print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                visited, check = backtrack(p, q, i, r, l, visited)
                print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                print('hi end')
                if check==1 or cnt>3: #스티커를 제대로 붙였을 때
                    i+=1
                    cnt=0

                if check==0 and cnt>3:
                    break
            if check==0 and cnt>3:
                break

    if i ==k:
        break

print("?????????")
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)

 

 

시간초과 해결후 틀린 코드 1

더보기
import sys, copy
# 스티커 각 칸은 상하좌우로 모두 연결되어있어야함 = 어디 끊기면 안됨
# 모눈종이에 스티커가 없는 불필요한 행이나 열이 있으면 정상아님

# 다른 스티커와 겹침X, 노트북 벗어나면X, 
# 노트북의 위쪽부터 채움(우선순위 = 1. 가장 위쪽의 위치
# , 위쪽에 여러곳일 경우2. 가장 왼쪽) 

# 만약 스티커 붙일 자리가 안보인다면, 스티커를 시계방향 90도 회전 후
# 붙일 자리 찾기

# 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 
# 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

# 다 붙인 후에 노트북에서 몇개의 칸이 채워졌는가?

#시간 복잡도
# 세로N(40) 가로M(40) 스티커의 개수K(100)

n, m, k = map(int, sys.stdin.readline().split())
stickerPocket = []
stickerPocketEntire=[]
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    tmp2=[]
    for j in range(r):
        tmp1 = list(map(int, sys.stdin.readline().split()))
        tmp2.append(tmp1)
    stickerPocket.append(tmp2)

#print(stickerPocket)

#스티커는 받았던것 부터 차례로 붙임 (최대한 붙여야하고 이런거 없음)
#전체에서 1번을 먼저 붙임. 
# 단, 붙일 수 없는 경우는 판별해줌 
# 붙일 수 없는 경우
#   1. 노트북을 벗어나는경우[조건문]
#   2. 회전을 시도해본다 (3회전) -> 그래도 벗어나면 버림[시계방향90도로 넘겨주는 함수]
# 붙일 수 있는 경우
#   ->맨위, 맨왼쪽에 붙인다. =>[전체에서 스티커 붙여졌는지 확인하는 배열]
#   ->다음 스티커로 넘어간다. 

# 두번째 스티커를 붙일때 부터는 스티커를 붙일 수 없는 자리를
# 피해서 스티커를 붙여야 한다. 

visited=[[0] * m for i in range(n)]

#print('visited', visited)

def rotation(cnt, i):
    
    r, l = stickerPocketEntire[i]
    # 3번만 돌림 
    
    #90도로 돌리기 
    tmp = stickerPocket[i]
    nn=len(tmp) 
    mm=len(tmp[0])
    tmpList =[]
    for qq in range(mm):
        tmpList2=[]
        for pp in range(nn-1, -1, -1):
            tmpList2.append(tmp[pp][qq])
        tmpList.append(tmpList2)
    #print('before',stickerPocket[i])
    stickerPocket[i]=tmpList
    
    stickerPocketEntire[i]=[mm, nn]
    # print('after',tmpList)
    # print('checkcnt: ', cnt)
    cnt+=1
    # print('cnt: ', cnt)
    return cnt

def backtrack(p, q, i, r, l, visited):
    rem = copy.deepcopy(visited)
    # print('초기 rem', rem)
    # 이부분 백트래킹으로 구현해야할듯 
    for j in range(r):
        for jj in range(l):
            #왼쪽 위부터 조회
            nr = p+j
            nl = q+jj
            if 0<=nr<n and 0<=nl<m:
                # print('i:', i, "j:", j, "jj", jj)
                if stickerPocket[i][j][jj]==1:
                    #두번째 스티커를 자리에 붙여본다
                    if visited[p+j][q+jj]!=1:
                        #겹치는지 확인후 붙여보기
                        visited[p+j][q+jj]=1
                    else:
                        #겹친다.
                        #스티커를 붙이기 전으로 원래대로 돌아가기 
                        visited=copy.deepcopy(rem)
                        check = 0
                        # print('리턴 visited', visited)
                        return visited, check
                check = 1
            else:
                # print("뭐지 nr:", nr, "nl:", nl)
                visited=copy.deepcopy(rem)
                check = 0
                # print('리턴 visited', visited)
                return visited, check
                
    if check == 1:
        return visited, check

i=0
cnt=0 # 회전 횟수
check=1
while(1):
    r, l = stickerPocketEntire[i]
    if (n<r or m<l) or check ==0: 
        if l<=n and r<=m:
        # 모눈종이의 크기가 노트북을 넘어가면
        # 90도 회전 적용
        # 회전의 조건: 모눈종이가 노트북에 딱 들어맞지 않는경우(단, 가로 세로는 뒤집혔을때 노트북에 들어갈 수 있어야 함)
            if cnt <3:
                cnt = rotation(cnt, i)
                r, l = stickerPocketEntire[i]
            else:
                i+=1
                cnt=0
        else:
            i+=1
    
    if i == 0:
        #항상 0,0에서 부터 시작해야함
        for j in range(r):
            for jj in range(l):
                if stickerPocket[0][j][jj]==1:
                    visited[j][jj]=1
        i+=1
        cnt=0
    else:
        for p in range(n):
            for q in range(m):
                if i ==k:
                    break
                r, l = stickerPocketEntire[i]
                # print('hi')
                # print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                visited, check = backtrack(p, q, i, r, l, visited)
                # print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                # print('hi end')
                if check==1 or cnt>3: #스티커를 제대로 붙였을 때
                    i+=1
                    cnt=0

                if check==0 and cnt>3:
                    break
            if check==0 and cnt>3:
                break

    if i ==k:
        break

#print("?????????")
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)

 

 

시간초과 해결후 틀린 코드 2

더보기
import sys
#import copy
# 스티커 각 칸은 상하좌우로 모두 연결되어있어야함 = 어디 끊기면 안됨
# 모눈종이에 스티커가 없는 불필요한 행이나 열이 있으면 정상아님

# 다른 스티커와 겹침X, 노트북 벗어나면X, 
# 노트북의 위쪽부터 채움(우선순위 = 1. 가장 위쪽의 위치
# , 위쪽에 여러곳일 경우2. 가장 왼쪽) 

# 만약 스티커 붙일 자리가 안보인다면, 스티커를 시계방향 90도 회전 후
# 붙일 자리 찾기

# 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 
# 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

# 다 붙인 후에 노트북에서 몇개의 칸이 채워졌는가?

#시간 복잡도
# 세로N(40) 가로M(40) 스티커의 개수K(100)

n, m, k = map(int, sys.stdin.readline().split())
stickerPocket = []
stickerPocketEntire=[]
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    tmp2=[]
    for j in range(r):
        tmp1 = list(map(int, sys.stdin.readline().split()))
        tmp2.append(tmp1)
    stickerPocket.append(tmp2)

#print(stickerPocket)

#스티커는 받았던것 부터 차례로 붙임 (최대한 붙여야하고 이런거 없음)
#전체에서 1번을 먼저 붙임. 
# 단, 붙일 수 없는 경우는 판별해줌 
# 붙일 수 없는 경우
#   1. 노트북을 벗어나는경우[조건문]
#   2. 회전을 시도해본다 (3회전) -> 그래도 벗어나면 버림[시계방향90도로 넘겨주는 함수]
# 붙일 수 있는 경우
#   ->맨위, 맨왼쪽에 붙인다. =>[전체에서 스티커 붙여졌는지 확인하는 배열]
#   ->다음 스티커로 넘어간다. 

# 두번째 스티커를 붙일때 부터는 스티커를 붙일 수 없는 자리를
# 피해서 스티커를 붙여야 한다. 

visited=[[0] * m for i in range(n)]

#print('visited', visited)

def rotation(cnt, i):
    
    r, l = stickerPocketEntire[i]
    # 3번만 돌림 
    
    #90도로 돌리기 
    tmp = stickerPocket[i]
    nn=len(tmp) 
    mm=len(tmp[0])
    tmpList =[]
    for qq in range(mm):
        tmpList2=[]
        for pp in range(nn-1, -1, -1):
            tmpList2.append(tmp[pp][qq])
        tmpList.append(tmpList2)
    #print('before',stickerPocket[i])
    stickerPocket[i]=tmpList
    
    stickerPocketEntire[i]=[mm, nn]
    # print('after',tmpList)
    # print('checkcnt: ', cnt)
    cnt+=1
    # print('cnt: ', cnt)
    return cnt

def backtrack(p, q, i, r, l, visited):
    #rem = copy.deepcopy(visited)
    rem = [item[:] for item in visited]
    # print('초기 rem', rem)
    # 이부분 백트래킹으로 구현해야할듯 
    for j in range(r):
        for jj in range(l):
            #왼쪽 위부터 조회
            nr = p+j
            nl = q+jj
            if 0<=nr<n and 0<=nl<m:
                # print('i:', i, "j:", j, "jj", jj)
                if stickerPocket[i][j][jj]==1:
                    #두번째 스티커를 자리에 붙여본다
                    if visited[p+j][q+jj]!=1:
                        #겹치는지 확인후 붙여보기
                        visited[p+j][q+jj]=1
                    else:
                        #겹친다.
                        #스티커를 붙이기 전으로 원래대로 돌아가기 
                        #visited=copy.deepcopy(rem)
                        visited=[item[:] for item in rem]
                        check = 0
                        # print('리턴 visited', visited)
                        return visited, check
                check = 1
            else:
                # print("뭐지 nr:", nr, "nl:", nl)
                #visited=copy.deepcopy(rem)
                visited = [item[:] for item in rem]
                check = 0
                # print('리턴 visited', visited)
                return visited, check
                
    if check == 1:
        return visited, check

i=0
cnt=0 # 회전 횟수
check=1
while(1):
    r, l = stickerPocketEntire[i]
    if (n<r or m<l) or check ==0: 
        if l<=n and r<=m:
        # 모눈종이의 크기가 노트북을 넘어가면
        # 90도 회전 적용
        # 회전의 조건: 모눈종이가 노트북에 딱 들어맞지 않는경우(단, 가로 세로는 뒤집혔을때 노트북에 들어갈 수 있어야 함)
            if cnt <3:
                cnt = rotation(cnt, i)
                r, l = stickerPocketEntire[i]
            else:
                i+=1
                cnt=0
        else:
            i+=1
    
    if i == 0:
        #항상 0,0에서 부터 시작해야함
        for j in range(r):
            for jj in range(l):
                if stickerPocket[0][j][jj]==1:
                    visited[j][jj]=1
        i+=1
        cnt=0
    else:
        for p in range(n):
            for q in range(m):
                if i ==k:
                    break
                r, l = stickerPocketEntire[i]
                # print('hi')
                # print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                visited, check = backtrack(p, q, i, r, l, visited)
                # print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                # print('hi end')
                if check==1 or cnt>3: #스티커를 제대로 붙였을 때
                    i+=1
                    cnt=0

                if check==0 and cnt>3:
                    break
            if check==0 and cnt>3:
                break

    if i ==k:
        break

#print("?????????")
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)


정답 코드

 

import sys

n, m, k = map(int, sys.stdin.readline().split())

# 스티커 받고, 스티커 함에 넣기 
stickerPocket = []     # 모눈종이 각각 map 모음
stickerPocketEntire=[] # 모눈종이 r, l 모아놓는 함
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    sticker=[]
    for j in range(r):
        tmp = list(map(int, sys.stdin.readline().split()))
        sticker.append(tmp)
    stickerPocket.append(sticker)

# 붙여졌나요?
visited=[[0] * m for i in range(n)]

# 90도 회전 함수
def rotation(cnt, i): 
    r, l = stickerPocketEntire[i] 
    # (90, 180, 270)도에 해당하게끔 3번만 돌림 
    
    sticker = stickerPocket[i]
    nn=len(sticker) # 기존의 행을 열로 사용
    mm=len(sticker[0]) # 기존의 열을 행으로 사용
    newSticker =[]

    for qq in range(mm):
        tmp=[]
        for pp in range(nn-1, -1, -1):
            tmp.append(sticker[pp][qq])
        newSticker.append(tmp)
    stickerPocket[i]=newSticker
    stickerPocketEntire[i]=[mm, nn]
    # 회전카운트 증가시킴
    cnt+=1  
    return cnt

def backtrack(p, q, i, visited):
    # 스티커를 붙일 수 없을 경우, 붙이려는 스티커를 붙이기 전 상태로 돌아오기위한것 
    rem = [item[:] for item in visited]
    # 붙이려는 스티커의 행열 조회
    r, c = stickerPocketEntire[i] 
    for j in range(r): # 행
        for jj in range(c): # 열
            #왼쪽 위부터 조회
            nr = p+j # 행/ 기존 행:n
            nc = q+jj # 열/ 기존 열:m
            # 스티커가 노트북 범위 안에 들어오면
            if 0<=nr<n and 0<=nc<m: 
                # 모눈종이에서 붙여야 하는 지점인 경우 
                if stickerPocket[i][j][jj]==1:
                    # 미리 붙여진 스티커가 없다.
                    if visited[nr][nc]!=1:
                        # 스티커 붙이기 
                        check = 1
                        visited[p+j][q+jj]=1
                    # 미리 붙여진 스티커가 있다. 겹친다.
                    else:
                        # 스티커를 붙이기 전(원래대로)으로 돌아가기 
                        visited=[item[:] for item in rem]
                        # 스티커를 못붙였다는 표식
                        check = 0
                        return visited, check
                # 모눈종이에서 빈칸인 경우
                else:
                    continue
            # 스티커가 노트북 범위 안에 못을어오면
            else:
                # 노트북 원상복구
                visited = [item[:] for item in rem]
                # 스티커를 못붙였다는 표식
                check = 0
                return visited, check
    # 스티커를 정상적으로 붙인 경우
    if check == 1:
        return visited, check

# 시작 스티커 번호
i=0
# 붙일 스티커 번호가 증가한 후 한번도 스티커를 회전한적 없는경우를 가리기 위한 변수
first=1
# 회전 횟수
cnt=0 
# 스티커를 붙이지 않았다는 표식
check=0
needRotation=0
while(1):
    rotat=0
    
    # 붙일 스티커가 없는 경우 종료 
    if i == k:
        break

    # 스티커의 행 열
    r, c = stickerPocketEntire[i]

    # 스티커의 행이나 열중 하나가 노트북을 벗어나는 경우
    if n<r or m<c: 
        #스티커의 행을 열로, 열을 행으로 바꿨을때(회전했을 때) 노트북에 들어가는 경우
        if c<=n and r<=m: 
            if cnt <3: # 0-0, 90-1, 180-2, 270-3
                cnt = rotation(cnt, i)
                rotat = 1
            # 회전을 4번다 한 경우, 다음 스티커로 넘어감 
            else:
                i+=1
                cnt=0
                first=1
        # 스티커의 행을 열로, 열을 행으로 바꿨을때(회전했을 때) 노트북에 안들어가는 경우=붙일 수 없는 경우
        elif c>n and r>m:
            # 다음 스티커로 넘어감 
            first=1
            i+=1
            cnt=0

    # 스티커가 붙여지지 않았고 0도 일때 스티커 붙이는 시도를 다 해본경우 
    # 회전이 270까지 되지 않은 경우=> 회전
    if check == 0 and first==0 and rotat==0 and needRotation==1:
        if cnt<=3:
            cnt = rotation(cnt, i)
        
    for p in range(n):
        for q in range(m):
            # 한번 돌았으면 first=0
            rotat=0
            first=0
            
            # 붙일 스티커가 없는 경우 종료 
            if i == k:
                break

            # 붙일 스티커 붙여보기 
            visited, check = backtrack(p, q, i, visited)
            #스티커를 제대로 붙였으면 빠져나감
            if check==1: 
                break
        #다음으로 붙일 스티커로 넘어가고, 회전 횟수 초기화
        #스티커를 붙였으면 빠져나감
        if check==1:
            check = 0
            i+=1
            cnt=0
            first=1
            needRotation=0
            break
    if check == 0:
        needRotation=1

    if cnt==3:
        i+=1
        first=1
        cnt=0

    # 붙일 스티커가 없는 경우 종료 
    if i ==k:
        break

# 노트북에 붙여진 스티커 개수 세기
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)

 

시간/공간 복잡도

 

N(40)*M(40)*K(100) = 16000이라  1초 기준으로 NlogN으로 풀어야 하는 문제이다. 근데 문제에서 2초를 주엇으니 사실상

2NlogN인건가? 

스터디할 때 물어볼 것 

 

 

최적화 및 개선

 

  • sys 모듈 sys.stdin.readline()을 사용하여 반복적으로 입력받을 때 input()에 비해 시간을 확 줄였음
  • copy 모듈의 deepcopy함수는 시간을 많이 잡아먹으니, 다른 방식으로 리스트 복사를 해야 시간초과가 나지 않는다. 

 

 

어려웠던 점

문제를 정확하게 이해해야 빈틈없이 구현할 수 있다. 

시뮬레이션 문제는 빈틈없이 구현하는게 가장 중요한것 같다. 

 

알게된 것

  • copy모듈의 deepcopy()함수는 시간을 많이 잡아먹는다. 따라서 다른 방법을 이용해야한다.(시간초과의 주범)

[CPU 아키텍처란?]

:하드웨어(CPU)가 작동하는 방식을 나타냄. =마이크로 아키텍처(microarchitecture) 

상용화된 제품은 ARM(RISC)과 x86(CISC) 등이 대표적인 예시임

 

[왜 알아야 하는가?]

: 서비스나 애플리케이션 운영 환경은 하드웨어에서부터 다양함. 

- arm, x86과 같은 CPU부터 GPU, TPU, NPU 까지 고려해야할 환경이 많음

- 사용 가능한 리소스의 제한에 따라서 개발 방향도 변경될 수 있음 

(리소스는 CPU, 메모리, Storage 성능, 네트워크 성능 등 다양한 변수가 존재함)

 

[실무와 가까운 중요이야기]

  • 리눅스의 멀티 유저 특성
    • 리눅스는 여러 사용자가 하나의 시스템에서 동시에 작업할 수 있는 환경을 지원함
    • 각 사용자는 자신의 환경, 파일, 프로세스 등을 독립적으로 관리할 수 있음
  • 컨테이너 기술
    • 이는 리눅스의 멀티유저를 지원하는 특성을 확장해서, 프로세스와 리소스 등을 격리된 환경에서 사용할 수 있도록 제공함
    • 이는 나아가서 클라우드 컴퓨팅과 마이크로서비스 아키텍처를 구성하는데 있어서 중요한 역할을 하게됨

[자주 겪는 문제]

  • Too many open files / 최대 생성 프로세스 제한
    • 설명 
      • 보안상의 이유로 Linux상에서 생성할 수 있는 프로세스나 프로세스가 열 수 있는 파일의 수 등을 제한하고 있음
      • 따라서 개발 과정에서 한번쯤은 마주치는 오류 중 하나임
        • 실제로 프로세스 생성이나 파일 접근을 동시에 다량으로 하거나, Socket 을 다루는 과정에서 발생할 수 있음
          (Socket이 파일로 관리되기 때문)
    • 해결 방법
      • /etc/security/limits.conf 혹은 ulimit 명령어를 통해 제한 값을 조정할 수 있음
  • 부모 프로세스 - 자식 프로세스의 관리
    • 필요에 따라서 자식 프로세스를 생성할 때, 부모 프로세스에 종속되지 않게 독립된 프로세스로 생성할 수 있음
    • 고아 프로세스/좀비 프로세스 문제
      • 시스템 자원이 낭비되고 있는 상태
      • 프로세스 관리의 어려움
      • PID(Process ID)의 낭비
        • 시스템에서 사용할 수 있는 PID 수가 제한되어 있으므로, 프로세스 생성이 불가능해질 수 있
  • OOM(Out of Memory)
    • 메모리 누수(Memory Leak)의 경우
      • 해결 방법
        • 메모리 누수 
          • APM(Application Performance Monitoring) 혹은 프로파일러를 활용해 메모리 누수 지점을 찾고 분석함
          • GC(Garbage Collector)최적화
          • 미사용 자원 해제
    • 계획된 메모리를 초과하는 요청이 오는 경우
      • (하드웨어적 제한) 32bit CPU의 경우 최대 4GB, 물리적인 메모리의 한계에 달하는 경우 등
      • (소프트웨어적 제한) JVM이나 V8 Engine(Node.js에서 관리하는 것) 등에서 제한하는 경우 등
      • 해결 방법
        • 정답은 없음=설득의 영역
        • 상황에 가장 알맞은 사양을 결정해야함. 어떨 때는 스펙 변경으로, 또 다룰 상황에서 코드의 최적화 등으로 해결할 수 있다. 
        • 트레이드 오프를 고려하여 적절한 선택을 할 수 있어야 한다. 

 

 

'클라우드' 카테고리의 다른 글

클라우드  (0) 2024.07.31

[사용 이유]

  • 비용절감
    • 온프레미스 환경(하드웨어를 직접 구매해서 인프라를 구성하는 환경)과 달리, 사용한만큼 비용을 지불하기 때문에 비용 절감 효과가 큼
  • 유연한 확장
    • 리소스 사용량, 네트워크 트래픽을 기준으로 사용량에 맞게 확장과 축소를 할 수 있음
  • 효율적인 관리
    • 인프라 유지보수도 클라우드 벤더사에서 담당하기 때문에 제품 개발에 집중 할 수 있음

[위험성]

  • 데이터 보안
    • 데이터가 클라우드 환경에 저장됨으로써, 온프레미스 환경에 비해 훨씬 해킹이나 데이터 유출 위험에 노출되기 쉬움
  • 서비스 가용성
    • 클라우드 벤더사에 의존하는 격으로, 해당 벤더사에 문제가 생기면 그대로 서비스 문제로 연결됨
  • 비용 관리의 어려움
    • 확장이 쉽게 이루어지는 만큼, 이를 효과적으로 관리하지 못할 경우 비용이 필요 이상으로 지불하게 되는 경우가 발생할 수 있음

[반면 온프레미스 환경의 적합한 사용 환경은??]

  • 보안에 민감한 서비스에서 사용 ex) 금융권, 의료, 정부 사업 등 인프라, 데이터가 물리적으로 분리되어있을 필요가 있는 경우
  • 낮은 지연 시간이 필요한 서비스에서 사용 ex)게임 산업, 금융 서비스, 군사 산업 등의 산업에서 필요로함 

 

클라우드의 단점을 보완하기 위한 조짐

  • 하이브리드 클라우드, 멀티 클라우드 등 다양한 형태의 클라우드 사용 사례들이 나타나고 있음

[이분탐색(이진탐색)의 정의]

: 오름차순으로 정렬된 리스트에서 찾고자 하는 값의 위치를 찾는 알고리즘


[이분탐색의 특징]

: 모든 값을 순회하는 일반탐색 보다 빠른 속도.

시간복잡도: O(logN)


[이분 탐색 진행 과정]

 

오름차순으로 정렬된 배열에서 특정 데이터를 탐색하는 상황

 

1. 오름차순으로 정렬된 배열에서 맨처음 값의 인덱스를 low라고 하고, 맨 뒤의 인덱스의 값을 high의 변수에 저장함. 

 

2.  배열의 중앙값을 선택: 중앙값은 mid가 된다. 

 

3. 중앙값과 특정 데이터의 값을 비교한다. 

만약, 중앙값보다 특정 데이터가 클 경우, 중앙값 오른쪽의 요소들을 선택하여 탐색한다. = 탐색을 대상 배열의 맨 처음 인덱스의 값(low)가 중앙값+1의 값을 가진다. 

 

(반대의 경우는 high가 중앙값-1의 값을 가짐)

 

4. 선택된 우측 요소의 중앙값을 선택한다. (mid갱신)

 

5. 중앙값과 특정 데이터를 비교한다.

만약, 특정 데이터가 중앙값 보다 클 경우,중앙값 오른쪽의 요소들을 선택하여 탐색한다. = 탐색을 대상 배열의 맨 처음 인덱스의 값(low)가 중앙값+1의 값을 가진다.

 

(반대의 경우는 high가 중앙값-1의 값을 가짐)

 

6. 남은 배열(2개)중에 좌측 요소를 선택하여 특정 데이터를 비교한다. 

만약 두 값이 같으면 탐색 종료 

 


[구현]

 

  • 반복문
  • 재귀

1. 탐색 대상값이 속한 자료를 오름차순으로 정렬한다. 

2. 자료의 중간값(mid)이 찾고자 하는 값(target)인지 비교한다. 

3. 중간값(mid)이 찾고자 하는 값(target)과 다르다면 대소관계를 비교하여 탐색 범위를 좁히고, target과 mid값이 같을 때까지 아래 조건에 따라 2번 3번을 반복한다. 

  1. 찾고자 하는 값(target)이 중간값(mid)보다 작으면 end값을 mid-1의 값으로 바꾼다. 
  2. 찾고자 하는 값(target)이 중간값(mid)보다 크면 high값을 mid+1의 값으로 바꾼다.

 

 

https://www.acmicpc.net/problem/19637

 

 

 

문제 해결 방법

시간복잡도를 고려햐여 이분탐색을 통해 문제를 풀이해야 함 

 

코드 구현

 

시간초과난 코드

더보기
# 전투력 10,000 이하의 캐릭터는 WEAK
# 10,000 초과 그리고 100,000 이하의 캐릭터는 NORMAL
# 100,000 초과 그리고 1,000,000 이하의 캐릭터는 STRONG 칭호
n, m = map(int, input().split())
check=[]
for i in range(n):
    k, value = input().split()
    check.append([k, int(value)])
#받은값 정렬해줌 
# check = sorted(check, key = lambda x: x[1])

tmp = []
# 값 받고
for i in range(m):
    value = int(input())
    tmp.append(value)

tmp.sort()

cnt = 0
for i in tmp:
    if i <= check[cnt][1]:
        print(check[cnt][0])
    else:
        while 1:
            cnt+=1
            if i <= check[cnt][1]:
                print(check[cnt][0])
                break

 

 

# 전투력 10,000 이하의 캐릭터는 WEAK
# 10,000 초과 그리고 100,000 이하의 캐릭터는 NORMAL
# 100,000 초과 그리고 1,000,000 이하의 캐릭터는 STRONG 칭호
n, m = map(int, input().split())
check=[]
for i in range(n):
    k, value = input().split()
    check.append([k, int(value)])
#받은값 정렬해줌 
# check = sorted(check, key = lambda x: x[1])

tmp = []
# 값 받고
for i in range(m):
    value = int(input())
    tmp.append(value)

tmp.sort()
############################
mid, high, low = (n-1)//2, n-1, 0

print('mid, high, low:', mid, high, low)

for i in range(m):
    target = tmp[i]
    print("target:", target)
    print("check:", check)
    while 1:
        if target <= check[mid][1]:
            print(check[mid][0])
            break
        elif target > check[mid][1]:
            low = mid+1
            mid = (low+high)//2
        elif target < check[mid][1]:
            high = mid-1
            mid = (low+high)//2

 

bisect 모듈을 통해 풀이한 코드

from bisect import bisect_left
import sys
# 전투력 10,000 이하의 캐릭터는 WEAK
# 10,000 초과 그리고 100,000 이하의 캐릭터는 NORMAL
# 100,000 초과 그리고 1,000,000 이하의 캐릭터는 STRONG 칭호
n, m = map(int, input().split())
checkChing=[]
checkValue=[]

for i in range(n):
    k, value = sys.stdin.readline().split()
    checkChing.append(k)
    checkValue.append(int(value))

# 값 받고
tmp = []
for i in range(m):
    value = int(sys.stdin.readline())
    tmp.append(value)

for i in range(m):
    target = tmp[i]
    tmpIndex=bisect_left(checkValue, target)
    print(check2[tmpIndex])

 

bisect 모듈 없이 풀이한 코드

 

import sys

# 전투력 10,000 이하의 캐릭터는 WEAK
# 10,000 초과 그리고 100,000 이하의 캐릭터는 NORMAL
# 100,000 초과 그리고 1,000,000 이하의 캐릭터는 STRONG 칭호
n, m = map(int, sys.stdin.readline().split())
check=[]
for i in range(n):
    k, value = sys.stdin.readline().split()
    check.append([k, int(value)])
#받은값 정렬해줌 
check = sorted(check, key = lambda x: x[1])

tmp = []
# 값 받고
for i in range(m):
    value = int(sys.stdin.readline().rstrip())
    tmp.append(value)

for i in range(m):
    high = len(check)
    low = 0
    result = 0
    target = tmp[i]
    while low <= high:
        mid = (low + high)//2
        if target <= check[mid][1]:
            high = mid-1
            result = mid
        elif target > check[mid][1]:
            low = mid+1
    print(check[result][0])

 

시간/공간 복잡도

 

모든 경우를 다 조회할 경우 N은 100만까지 가능함. 

이 경우, 시간 초과를 면하기 위해서는 O(NlogN)의 시간복잡도가 되게끔 코드를 작성해야 한다. 

 

(기존에 작성한 코드에서 사용한 이중 반복문으로 구현하면 안됨,,)

 

(백준한정)추가시간 없음 이라는 말은 파이썬은 기존에 1초에 2천만번 연산 가능함 원래는 이런말 없으면 1초에 1억번 연산가능함 

 

최적화 및 개선

 

sys 모듈 sys.stdin.readline()을 사용하여 반복적으로 입력받을 때 input()에 비해 시간을 확 줄였음

 

 

어려웠던 점

이분탐색 개념이 가물가물 했다. 

 

어이없는 실수: 받은 값을 정렬함으로써 아무리 알고리즘을 맞게 짜도 출력 자체가 이상하게 되도록 함; (스스로 재앙을 불러옴)

 

알게된 것

  • 이분탐색
  • bisect 모듈
    •  
  • sys 모듈 sys.stdin.readline: 반복적으로 입력받을 때 input()에 비해 시간을 확 줄여줌
    • 단, 반복적으로 받지만 하나의 값만을 받는 경우 rstrip()을 통해 받은 값의 오른쪽에 붙는 개행문자를 지워줘야함. 

문제 상황: 원격저장소에 브랜치 하나 팟는데 포크딴 저장소에서는 해당 브랜치가 git pull을 해도, git fetch를 해도 안보임

 

해결을 위한 방법: 

 

개념:
포크된 저장소는 원본 저장소의 복사본

따라서 내 컴퓨터에서 직접 적인 연결은 포크된 저장소에 돼있음

따라서 새로 생성된 브랜치를 보려면 추가작업이 필요함. 

 

원본 저장소를 원격으로 추가해야함 

그 후 원본 저장소에서 fetch

그후 브랜치 확인과 체크아웃을 해야함


[해결 과정]

 

1. 원본 저장소를 원격으로 추가함. 

먼저, 원본 저장소를 upstream이라는 이름으로 원격 설정에 추가한다. 
이때 <original-repo-url>은 원본 저장소의 URL이다.

git remote add upstream <original-repo-url>
 

2. 원본 저장소로부터 fetch

upstream 원격 저장소로부터 최신 변경 사항을 가져온다. 

git fetch upstream
 

이 명령어는 원본 저장소의 모든 브랜치를 로컬 저장소로 가져오는 명령어이다. 

 

3. 새로운 브랜치 확인

원본 저장소에서 가져온 브랜치를 확인한다.

git branch -r
 

이 명령어는 모든 원격 브랜치를 보여준다.

 

여기서 upstream/<branch-name> 형식으로 원본 저장소의 브랜치를 볼 수 있다.

 

4. 새 브랜치 체크아웃

 

새로운 브랜치를 로컬에 체크아웃한다.

git checkout -b <new-branch-name> upstream/<new-branch-name>
 

이렇게 하면 원본 저장소의 새로운 브랜치를 로컬에 체크아웃할 수 있다. 

 

5. 변경 사항 포크된 저장소에 반영하기

필요하다면, 포크된 저장소에 변경 사항을 반영하기 위해 push할 수 있다.

git push origin <new-branch-name>

포크따온 원본 브랜치에 풀리퀘스트 하는 방법은? 

 

  1. 포크된 저장소에 변경 사항 푸시
     
    git add . git commit -m "설명 추가" git push origin <branch-name>
     
    이 명령어를 통해 <branch-name> 브랜치에 변경 사항이 반영된다.

  2. 먼저 포크된 저장소의 브랜치에서 필요한 모든 변경 사항을 완료하고 커밋한 후, 해당 브랜치를 포크된 저장소에 푸시한다.
  3. GitHub에 로그인하여 포크된 저장소로 이동한다.
  4. 웹 브라우저에서 GitHub에 로그인한 후, 포크된 저장소로 이동한다.
  5. New Pull Request 버튼 클릭
    • 포크된 저장소의 GitHub 페이지에서 "Pull Requests" 탭을 클릭한다.
    • "New Pull Request" 버튼을 클릭한다.
  6. 브랜치 비교 설정
    • Base Repository 설정: 원본 저장소와 그 안의 병합을 원하는 브랜치를 선택합니다. 일반적으로 main이나 develop 브랜치가 됩니다.
    • Head Repository 설정: 포크된 저장소와 그 안의 변경 사항이 있는 브랜치를 선택한다.
    GitHub는 기본적으로 포크된 저장소의 변경 사항을 원본 저장소의 브랜치에 비교하는 UI를 제공한다.

  7. 변경 사항 검토
    • 비교 화면에서 변경된 파일 목록과 커밋을 검토할 수 있다.
    • 모든 것이 올바른지 확인한다.
  8. Pull Request 제목과 설명 작성
    • Pull Request의 제목과 설명을 작성하여 변경 사항의 내용을 명확히 설명한다. 이때 변경의 이유, 수정한 내용, 테스트한 부분 등을 상세히 기술하면 좋다.
  9. 리뷰어 및 라벨 설정 (선택 사항)
    • 필요한 경우 리뷰어를 지정하여 검토를 요청할 수 있다.
    • 라벨이나 마일스톤을 설정하여 Pull Request의 성격을 명확히 할 수 있다.
  10. Create Pull Request 버튼 클릭
  11. 모든 설정이 완료되면 "Create Pull Request" 버튼을 클릭하여 Pull Request를 생성한다.

Pull Request 생성 후

  • 코드 리뷰: 원본 저장소의 관리자는 Pull Request를 검토하고 피드백을 줄 수 있다.
  • 수정 및 추가 커밋: 피드백이 있을 경우 로컬에서 수정을 진행한 후, 동일한 브랜치에 추가 커밋을 푸시하면 Pull Request에 자동으로 반영된다.
  • 병합: 모든 리뷰와 수정이 완료되면 원본 저장소 관리자가 Pull Request를 병합한다.

2과목 개요


[SQL기본] 01. 관계형 데이터베이스 개요

 

데이터베이스 관련 용어 정리 

 

  • 데이터베이스(DataBase, DB)
    • 데이터를 일정한 형태로 저장해 놓은 것 ex)엑셀
  • 데이터베이스관리시스템(DataBase Management System, DBMS)
    • 기존 데이터베이스 기능에 추가로 데이터 손상을 방지 및 복구, 인증된 사용자만 접근 등 추가 기능을 지원하는 관리 시스템
  • 관계형 DBMS (Relational DBMS, RDBMS)
    • 테이블로 데이터를 관리하고 테이블간 관계를 이용해 데이터를 정의하는 방식으로, 대부분 기업이 사용하며 지금 공부하는 Oracle도 RDBMS 중 하나임 

  • 테이블(Table)
    • RDBMS에서 실제 데이터가 저장되고 조회되는 2차원 배열 형태의 저장소 공간
    • 엔터티, 속성, 인스턴스가 각각 DB가 이해할 수 있는 형태인 테이블, 컬럼, 튜플로 변경된 것

 

  • SQL(Structured Query Language)
    • RDBMS에서 데이터 정의, 조작, 조회, 제어 등을 하기 위해 사용하는 언어
    • (매우 중요한 내용이니 종류별로 어떤 문법이 있는지 외워야함)
DDL(Data Definition Language, 데이터 정의어) CREATE, ALTER, DROP, RENAME, TRUNCATE
DML(Data Manipulation Language, 데이터 조작어) SELECT, INSERT, UPDATE, DELETE, MERGE
DCL(Data Control Language, 데이터 제어어) GRANT, REVOKE
TCL(Transaction Control Language, 트랜잭션 제어어) COMMIT, ROLLBACK, SAVEPOINT

 

 

STANDARD SQL 개요

일반집합연산자와 순수관계연산자

 


[SQL기본] 02. SELECT문

 

SELECT란?

:테이블에서 원하는 데이터를 조회할 때 사용하는 문법

 


SELECT CUST_ID, CUST_NAME, BIRTH_DY

FROM TB_CUST

WHERE MONEY = 10000;

 

1. TB_CUST 테이블에서(FROM) 데이터를 가져오겠습니다. 

2. TB_CUST 테이블에서 MONEY (보유금액)이 10000인 튜플(행)만 가져오겠습니다. 

3. 출력되는 튜플(행)에 대해 CUST_ID, CUST_NAME, BIRTH_DY 컬럼(열)만 가져오겠습니다. 

 


SELECT *FROM TB_PRD;

TB_PRD 테이블의 모든 컬럼 정보를 출력합니다. 

 

==> *(애스터리스크)는 SELECT뒤에 사용되며 테이블 내의 모든 컬럼 정보를 출력함.


SELECT DISTINCT PRD_TYPE FROM TB_PRD;

TB_PRD 테이블의 PRD_TYPE 컬럼을 기준으로 값을 중복없이 출력합니다. 

 

==> DISTINCT는 SELECT 뒤, 컬럼 앞에 사용되며 해당 컬럼 정보에 대해 중복을 제거함. 

 


==> AS는 SELECT 부분에서 출력하려는 컬럼에 대해 새로운 별명(ALIAS)를 부여할 수 있음.

AS 사용시 주의사항

1. 띄어쓰기 불가

2. 문자로 시작 해야함

3. 예약어 불가

4. 특수문자는 $, _, # 만 가능

 


SELECT에서 연결연산하기 ( || 기호 사용하기 )

 


SELECT에서 사칙연산하기 

 


 

+ Recent posts