UDP 통신이란? 

  • User Datagram Protocol, 데이터를 데이터그램 단위로 처리하는 프로토콜
  • 비연결형, 신뢰성 없는 전송 프로토콜
  • 데이터그램 단위로 쪼개면서 전송해야 하기 때문에 L4, transport Layer

 

TCP와 UDP가 나온 이유

  • IP의 역할은 Host to Host만을 지원한다. 하나의 장비 안에서 수많은 프로그램이 통신해야 할 경우는 IP만으로는 한계가 있다 
    • =>프로세스별 구분 지을 수 있는 Port 번호가 등장하게 됨
  • IP에서 오류가 발생하면 ICMP에서 알려준다. 하지만 알려주기만 할 뿐 대처는 못하므로 상위 프로토콜인 TCP와 UDP가 나오게 됐다
ICMP ? 인터넷 제어 메시지 프로토콜. 네트워크 컴퓨터 위에서 돌아가는 OS에서 오류 메시지를 전송받는데 쓰임

 

TCP와 UDP의 오류 해결법?

  • TCP: 데이터의 순차성과 분실, 중복 등을 자동으로 보정해줌으로써 송수신 데이터의 정확한 전달을 보장
  • UDP: IP가 제공하는 정도의 수준만을 제공하는 간단한 IP 상위 계층의 프로토콜, 확인 응답을 못하므로 TCP와 달리 에러가 날 수 있고 재전송이나 순서가 뒤바뀔 수 있는 번거로움 존재하며 신뢰도 떨어짐

 

UDP Header

  • Source Port: 시작 포트
  • Destination Port: 목적지 포트
  • Length: 길이
  • Checksum: 오류 검출
    • 중복 검사의 한 형태로, 오류 정정을 통해 자료의 무결성을 보호하는 단순한 방법

 

UDP는 왜 사용할까?

  • 데이터의 빠른 처리, 신속성 때문 !!
  • 주로 실시간 방송과 온라인 게임에서 사용됨
  • 네트워크 환경이 안 좋을 때, 끊기는 현상을 생각하자

 

DNS에서 UDP를 사용하는 이유

  • DNS Request의 양이 작음 -> UDP Segment에 담길 수 있음
  • 3 way handshaking으로 연결을 유지할 필요가 없음 (TCP는 Protocol Overhead가 큼 )
  • Request의 손실은 Application layer에서 제어함으로써 신뢰성이 추가될 수 있음 (Timeout이나 재전송 작업을 통해서)
  • DNS: port 53번
  • 하지만 TCP를 사용하는 경우?
    • Zone Transfer(=DNS 서버 간의 요청을 주고받을 때 사용하는 transter)를 사용해야 하는 경우! 
    • 데이터의 크기가 512바이트(UDP의 제한크기) 를 넘기거나 응답을 못받은 경우

 

TCP와 UDP 통신 실습

  • UDP

UDP server 코드

from socket import *

serverName = "192.168.0.108"
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind((serverName, serverPort)) # 12000 포트로 들어오는 프로세스를 소켓에 바인딩
print("The server is ready to receive")

while True:
    message, clientAddress = serverSocket.recvfrom(2048) # 서버에 들어온 정보를 2kbyte로 계속 읽어들임
    modifiedMessages = message.decode().upper() # 읽어드린 바이트 정보를 str로 변환 후 대문자화
    serverSocket.sendto(modifiedMessages.encode(), clientAddress) # 클라이언트 목적지 주소도 명시해서 보냄

UDP client 코드

from socket import *

serverName = "192.168.0.108"
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_DGRAM) # UDP의 소켓타입=SOCK_DRGAM
message = input("input lowercase sentence")

clientSocket.sendto(message.encode(), (serverName, serverPort)) # 서버의 IP 주소와 port 번호를 명시해서 서버에게 데이터 요청
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
print(modifiedMessage.decode())

clientSocket.close()

결과

  1. UDP는 비연결형 프로토콜로,  데이터 전송 시에 IP 주소와 port 번호를 명시해줘야 한다.
  2. sendTo() 메서드를 사용

 

  • TCP

TCP server 코드

from socket import *

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(("", serverPort))
serverSocket.listen(1)
print("The server is ready to receive")

while True:
    connectionSocket, addr = serverSocket.accept() # 요청을 기다리고 받은 후엔 connection 소켓이라는 새로운 분리된 연결을 만듬

    sentence = connectionSocket.recv(1024).decode()
    capitalizedSentence = sentence.upper()
    connectionSocket.send(capitalizedSentence.encode()) # UDP와 달리 connection이 있으므로 목적지 주소가 필요없음

    connectionSocket.close() # 이때 serverSocket은 닫지 않음

TCP client 코드

from socket import *

serverName = "192.168.0.108"
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM) # TCP의 소켓타입=SOCK_STREAM
clientSocket.connect((serverName, serverPort))
sentence = input("Input lowercase sentence")

clientSocket.send(sentence.encode()) # 서버의 목적지 주소 명시안해도 됨
modifiedSentence = clientSocket.recv(1024)
print("From Server: ", modifiedSentence.decode())
clientSocket.close()

결과

  1. TCP는 연결형 프로토콜로, 서버와 클라이언트는 항상 1대1로 연결되어서 통신하므로 따로 명시해줄 필요가 없다.
  2. send() 메서드를 사용

 

참고

https://gyoogle.dev/blog/computer-science/network/UDP.html

(개발자와 네트워크 관리자 모두에게 중요한 개념!★)

  • user단의 process가 실행(I/O 혹은 R/W)되면 커널단의 TCP/IP와 디바이스 드라이버를 거치고 NIC를 타고 인터넷으로 연결된다.
  • user model application이 TCP에 도달하기 위한 인터페이스가 Socket(=파일의 일종)
  • socket의 단위는 stream, 길이는 얼마나 긴지 모름
  • stream을 TCP에 보내기 위해 segment로 일정 단위로 댕강댕강 잘라냄
  • segment를 한번 encapsulate되면 packet 형식이 되고
  • packet이 또 encapsulate되면 frame이 된다.

<패킷의 구성>

  • 패킷 하나당 1500byte(MTU, Maximum Transfer Unit)정도
  • Header(!송장!)와 Payload(!택배!)로 나뉨

Header는 IP(L3, 20바이트)와 TCP(L4, 20바이트)로 나뉨

payload는 보통 1460바이트가 됨.

⇒ 즉, stream 데이터를 1460바이트씩 끊어내서 Payload로 넣는 것임!

  • DPI (Deep Packet Inspection) : payload까지 다 검사하는 것
  • encapsulate는 segment를 내용물이라고 한다면 이것을 포장해서 택배박스에 넣는 작업
  • frame은 트럭처럼, encapsulate된 패킷들을 다시 encapsulate하는 개념으로 생각하면 됨

 

출처(널널한한 개발자 TV-네트워크 기본 이론): https://www.youtube.com/playlist?list=PLXvgR_grOs1BFH-TuqFsfHqbh-gpMbFoy 

 

TCP 통신이란?

- 신뢰성과 순차성을 가진 네트워크 통신 방식

- 신뢰성 없는 네트워크 환경에서 reliable함을 보장

- network congestion avoidance 알고리즘 사용

 

신뢰성 보장 시 발생가능 문제

1. 손실 : 송수신 시 패킷 손실

2. 순서바뀜: 패킷 순서가 바뀜

3. congestion : 네트워크가 혼잡

4. overload : 수신자 버퍼 용량 초과

 

흐름제어

- 송수신 간 데이터 처리 속도 차를 해결하기 위한 기법

- 수신측이 송신측보다 처리속도가 느릴 때 문제 발생

- 수신측에서 제한된 용량이 초과 시 데이터가 손실될 수 있으며, 손실된다면 불필요하게 응답과 데이터 전송이 송수신 간에 빈번이 발생하게 됨

 

🗝 해결법

1. Stop and Wait

- 매번 전송한 패킷에 대해 확인 응답을 받아야만 그 다음 패킷을 전송

 

2. Go Back N (Sliding Window)

- 수신측에서 설정한 window size (=송신 후 수신까지 동안 전송가능한 패킷의 수)만큼 세그먼트를 송신

- 송신측은 ack 프레임을 받으면 ack 프레임 수만큼 경계가 확장됨

- 송신측에서 ack가 안된 가장 오래된 패킷 번호부터 돌아가서 다시 순서대로 보낸다

 

혼잡제어

- 송신측의 데이터는 지역망이나 대형 네트워크를 통해 전달된다. 만약 한 라우터에 데이터가 몰릴 경우, 자신에게 온 데이터를 모두 처리할 수 없게 된다. 혼잡이 가중되면 오버플로우나 손실이 발생할 수 있다.

- 따라서 송신측에서 보내는 데이터의 전송속도를 강제로 줄임

- 혼잡: 네트워크 내에서 패킷 수가 과도하게 증가하는 현상

- 혼잡제어: 혼잡현상을 방지하거나 제거하는 기능

 

🗝 해결법

1. AIMD (Addictive Increase/Multiplicative Decrease)

- 처음에 패킷을 하나씩 보내고 문제가 없으면 window size를 1씩 증가시키며 전송 ws+1

- 전송실패하거나 time-out이 되면 패킷 보내는 속도를 절반으로 줄임  ws*0.5

- 네트워크에 참여한 순서와 관계없이 모든 호스트의 window size가 평행 상태로 수렴하게 되는 특징.

- 문제점: 네트워크의 대역폭이 펑펑 남아도는 상황에도 너무 조금씩 늘리면서 접근하므로, 제대로 된 속도로 통신하기까지 시간이 오래 걸림

 

2. Slow start

- 처음에 패킷 하나씩 보내고, 문제가 없으면 각각의 ack 패킷마다 window size를 1씩 늘려주기 때문에 한 주기가 지나면 window size가 2배로 된다.  지수함수꼴로 증가

- 혼잡현상 발생 시 window size를 1로 떨어뜨림 -> 혼잡현상이 발생했던 window size의 절반까지는 지수함수꼴로 증가 -> 그 이후는 1씩 증가

 

3. 빠른 재전송

- 혼잡제어 추가된 정책 중 하나

- 먼저 도착해야 할 패킷이 나중에 도착하더라도 ack 패킷을 보냄

- 단, 순서대로 잘 도착한 마지막 패킷의 다음 패킷 순번을 ack 패킷에 실어서 보내므로, 중간에 하나가 손실되면 송신 측에서 순번이 중복된 ack 패킷을 받게 되고, 이를 감지하는 순간 재전송함

- 중복 패킷을 3번 받으면 재전송 -> 혼잡감지하고 window size를 줄임

- Tahoe는 기본적으로 Slow start를 사용하다가 혼잡이 느껴지면 AIMD로 전환하는데, 빠른 재전송을 처음으로 도입한 방법

 

4. 빠른 회복

- 혼잡한 상태가 되면 window size를 1로 줄이지 않고 반으로 줄이고 선형증가시킴

- 빠른 회복을 적용하면 혼잡상황을 한번 겪고 나면 순수한 AIMD 방식으로 동작함

- Reno는 빠른 회복을 도입한다.

 

참고

https://evan-moon.github.io/2019/11/26/tcp-congestion-control/

https://gyoogle.dev/blog/computer-science/network/%ED%9D%90%EB%A6%84%EC%A0%9C%EC%96%B4%20&%20%ED%98%BC%EC%9E%A1%EC%A0%9C%EC%96%B4.html

22. this

1. this 키워드

  • 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.
  • 객체 리터럴 방식의 경우 메서드 내부에서 식별자를 재귀적으로 참조할 수 있다.
  • 생성자 함수 방식의 경우 생성자 함수를 정의하는 시점에서는 아직 인스턴스를 생성하기 전이므로 인스턴스를 가리키는 식별자를 알 수 없다.

this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기참조변수다.

⇒ this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.

  • 함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다.
  • 단, this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.

** 바인딩? 식별자와 값을 연결하는 과정

2. 함수 호출방식과 this 바인딩

자바스크립트에서 this 바인딩은 함수의 호출 시점에 따라 동적으로 결정된다

  • 1. 일반함수 호출
    • 일반함수로 호출된 모든 함수(중첩함수, 콜백함수 포함) 내부의 this에는 전역 객체(window)가 바인딩된다.
    • strict 모드가 적용된 일반 함수 내부의 this에는 undefined가 바인딩된다
    • 하지만, 메서드 내의 중첩함수 또는 콜백함수의 this가 외부함수의 this와 일치하지 않는 것은 헬퍼함수로서 동작을 어렵게 한다.
      • Function.prototype.apply Function.prototype.call Function.prototype.bind 로 명시적으로 this를 일치시킬 수 있다
      • 화살표 함수를 사용해서 this를 일치시킬 수 있다
      • ( 화살표 함수는 자신만의 this를 갖지 않기 때문에 lexical scope를 갖게 된다. 그래서, 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다 )
  • 2. 메서드 호출
    • 점 연산자를 통해 객체의 메서드로서 호출되는 함수의 내부에서 this는 점 연산자(.) 앞에 있는 객체에 바인딩된다.
    • 이를 암묵적 바인딩이라고 부른다.
    • const person = { name: 'Lee', getName() { return this.name; } } console.log(person.getName()); //Lee
    • getName 메서드는 person 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체다.
      (새롭게 알게 됨!! 흥미돋!!)
      그저 getName 프로퍼티가 함수 객체를 가리키고 있을 뿐이다. ⇒ 그렇기 때문에 this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩되는 것인가??
    • 그래서 getName 프로퍼티가 가리키는 함수객체, 즉 getName 메서드는 다른 객체의 메서드도 될 수 있고, 일반 변수에 할당해서 일반함수로 호출될 수 있다.
    • const anoterPerson = { name: "kim" } anotherPerson.getName = person.getName // this는 anotherPerson const getName = person.getName // this는 window
    • 프로토타입 메서드 내부에서 사용되는 this도 일반 메서드와 마찬가지로 호출한 객체에 바인딩된다.
  • 3. 생성자 함수 호출
    • new 연산자와 함께 호출되는 경우 함수는 생성자로써 호출된다
    • 생성자 함수 내부에서 this는 생성할 객체를 의미한다.
    • // 생성자 함수 function Person(name) { this.name = name; } var me = new Person('Lee'); console.log(me); // Person {name: "Lee"} // new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다. var you = Person('Kim'); console.log(you); // undefined
  • 4. Function.prototype~ 객체의 메서드에 의한 간접호출
    • Function~~ 객체는 this를 명시적으로 바인딩할 수 있는 메서드를 제공한다.
    • apply, call
      • this로 사용할 객체와 인수 리스트를 인수로 전달받아 함수를 호출한다
      • 본질적인 기능은 함수를 호출하는 것이다. 함수를 호출하면서 첫 번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩한다.
      • 대표적인 용도는 arguments 객체와 같은 유사배열객체에 Array.prototype.slice 같은 배열 메서드를 사용하는 경우다.
      • function convertArgsToArray() { console.log(arguments); // arguments 객체를 배열로 변환 // slice: 배열의 특정 부분에 대한 복사본을 생성한다. var arr = Array.prototype.slice.apply(arguments); // arguments.slice // var arr = [].slice.apply(arguments); console.log(arr); return arr; } convertArgsToArray(1, 2, 3); // [1,2,3]
    • bind
      • this로 사용할 객체로 this 바인딩이 교체된 함수를 새롭게 생성해 반환한다.
      • apply와 call 과 달리 함수를 호출하지 않으므로 명시적으로 호출해야 한다.
      • const person = { name: "Lee", foo(callback) { setTimeout(callback.bind(this), 100); }, }; person.foo(function () { console.log(`Hi! my name is ${this.name}`); }); // Hi! my name is Lee

정리

함수호출방식 this 바인딩
일반함수 호출 전역 객체
메서드 호출 메서드를 호출한 객체
생성자 함수 호출 생성자 함수가 (미래에) 생성할 인스턴스
Function.prototype.apply Function.prototype.call Function.prototype.bind 메서드에 의한 간접 호출 Function.prototype.apply Function.prototype.call Function.prototype.bind 메서드에 첫번째 인수로 전달한 객체

추가1 : this 바인딩 우선순위

new 연산자에 의한 바인딩 > 명시적 바인딩 > 암시적 바인딩 > 일반함수 호출에 의한 바인딩

  • bind 메서드에 의한 바인딩이 apply, call에 의한 바인딩보다 우선순위가 높다
  • apply, call, bind 실행 시 이를 호출한 함수가 화살표 함수면 첫번째 인자인 thisArg가 무시된다. (화살표 함수는 자신만의 this, argument가 없기 때문에 자신의 lexical scope인 window가 this가 된다. )
const person = {
  name: "Lee",
  foo(callback) {
    setTimeout(callback.bind(this), 100);
  },
};

person.foo(() => {
  console.log(`Hi! my name is ${this.name}`);
});

// window.name은 빌트인 프로퍼티로 ""이다.
// Hi! my name is

'엘리스 ai 트랙 > FE' 카테고리의 다른 글

Javascript - ES11 ( ES2020 )의 특징들  (0) 2022.04.04
CI와 CD  (0) 2022.03.31
http 1.1 과 http 2 의 차이점  (0) 2022.03.31
HTTP  (0) 2022.03.13
기본값+rest+spread  (0) 2022.03.07

문제 설명

여러 개의 쇠막대기를 레이저로 절단하려고 한다. 효율적인 작업을 위해서 쇠막대기를 아래에서 위로 겹쳐 놓고, 레이저를 위에서 수직으로 발사하여 쇠막대기들을 자른다. 쇠막대기와 레이저의 배치는 다음 조건을 만족한다.

  • 쇠막대기는 자신보다 긴 쇠막대기 위에만 놓일 수 있다. - 쇠막대기를 다른 쇠막대기 위에 놓는 경우 완전히 포함되도록 놓되, 끝점은 겹치지 않도록 놓는다.
  • 각 쇠막대기를 자르는 레이저는 적어도 하나 존재한다.
  • 레이저는 어떤 쇠막대기의 양 끝점과도 겹치지 않는다.

아래 그림은 위 조건을 만족하는 예를 보여준다. 수평으로 그려진 굵은 실선은 쇠막대기이고, 점은 레이저의 위치, 수직으로 그려진 점선 화살표는 레이저의 발사 방향이다.

이러한 레이저와 쇠막대기의 배치는 다음과 같이 괄호를 이용하여 왼쪽부터 순서대로 표현할 수 있다.

  1. 레이저는 여는 괄호와 닫는 괄호의 인접한 쌍 ‘( ) ’ 으로 표현된다. 또한, 모든 ‘( ) ’는 반드시 레이저를 표현한다.
  2. 쇠막대기의 왼쪽 끝은 여는 괄호 ‘ ( ’ 로, 오른쪽 끝은 닫힌 괄호 ‘) ’ 로 표현된다.

위 예의 괄호 표현은 그림 위에 주어져 있다.

쇠막대기는 레이저에 의해 몇 개의 조각으로 잘려지는데, 위 예에서 가장 위에 있는 두 개의 쇠막대기는 각각 3개와 2개의 조각으로 잘려지고, 이와 같은 방식으로 주어진 쇠막대기들은 총 17개의 조각으로 잘려진다.

쇠막대기와 레이저의 배치를 나타내는 괄호 표현이 주어졌을 때, 잘려진 쇠막대기 조각의 총 개수를 구하는 프로그램을 작성하시오.

입력

한 줄에 쇠막대기와 레이저의 배치를 나타내는 괄호 표현이 공백없이 주어진다. 괄호 문자의 개수는 최대 100,000이다.

출력

잘려진 조각의 총 개수를 나타내는 정수를 한 줄에 출력한다.


문제해석

  • 쇠막대기와 레이저가 괄호로 표현되면서 순서대로 입력된다. 
  •  ')'를 찾았다면, 이전의 '(' 중에서도 ')'와 가장 가까운 '('와 짝을 맺어야 쇠막대기 하나가 된다.

그렇기 때문에 자료구조 스택을 사용한다.

 

ans라는 조각 수를 저장하는 변수,

현재까지 만들어질 예정인 막대기들 시작점을 담는 스택을 두자.

  • '(' 이라면, 스택에 넣는다.  
  • 순차적으로 입력값을 돌면서 '()'인 레이저를 찾았다면 개수는 ans += (현재 막대기의 수)이 된다.
  • ')'를 찾아서 막대기가 완성됐다면 꼬다리 조각 하나가 채워지기 때문에 ans += 1

 

입력값을 순차적으로 생각하지 않고 쇠막대기들이 중첩으로 쌓여있는 상태로, 전체적인 그림으로만 생각이 갇혔던 것 같다. 그래서 와.. 중첩으로 쌓여있는 쇠막대기들은 어떻게 구분해서 계산해야 하지? 고민이 들었다.

 

하지만 입력받은 값을 순차적으로 생각해가면서 논리를 따져보니, 중첩된 막대기수에 대한 풀이는 별 고려사항이 안 된것 같다.

 

코드

import sys
input = sys.stdin.readline().strip

ans = 0
st = []

brackets = list(input())

for i in range(len(brackets)):
  if(brackets[i] == '('):
    st.append("(")
  
  else:
    if(brackets[i-1] == '('): # 레이저!
      st.pop()
      ans += len(st)
    
    else: # 막대기 완성됨
      st.pop()
      ans += 1

print(ans)

 

코테 공부 많이 해야할 것 같다...!

'알고리즘 공부 > coding test' 카테고리의 다른 글

백준 17413 [단어 뒤집기 2]  (0) 2022.09.16
백준 1931 [회의실 배정]  (0) 2022.05.20
백준 20115 [에너지 드링크]  (0) 2022.05.20
백준 11399 [ATM]  (0) 2022.05.18
백준 2217[로프]  (0) 2022.05.17

문제 설명

문자열 S가 주어졌을 때, 이 문자열에서 단어만 뒤집으려고 한다.

먼저, 문자열 S는 아래와과 같은 규칙을 지킨다.

  1. 알파벳 소문자('a'-'z'), 숫자('0'-'9'), 공백(' '), 특수 문자('<', '>')로만 이루어져 있다.
  2. 문자열의 시작과 끝은 공백이 아니다.
  3. '<'와 '>'가 문자열에 있는 경우 번갈아가면서 등장하며, '<'이 먼저 등장한다. 또, 두 문자의 개수는 같다.

태그는 '<'로 시작해서 '>'로 끝나는 길이가 3 이상인 부분 문자열이고, '<'와 '>' 사이에는 알파벳 소문자와 공백만 있다. 단어는 알파벳 소문자와 숫자로 이루어진 부분 문자열이고, 연속하는 두 단어는 공백 하나로 구분한다. 태그는 단어가 아니며, 태그와 단어 사이에는 공백이 없다.

입력

첫째 줄에 문자열 S가 주어진다. S의 길이는 100,000 이하이다.

출력

첫째 줄에 문자열 S의 단어를 뒤집어서 출력한다.

해결법

1. 입력받은 문자열을 먼저 리스트로 변환

2. 리스트를 순회하면서 '<'이 나오는 조건문에서 '>'가 나올 때까지 이터레이터를 돌리기

3. 리스트를 순회하면서 숫자거나 문자열이 나오면 이터레이터가 S의 길이를 초과하지 않는 선에서 해당 구간을 뒤집은 문자열로 교체하기

4. 리스트를 순회하면서 공백이라면 그냥 이터레이터를 돌리기

코드

S = list(input())

i=0
start = 0

while(i < len(S)):
  if(S[i] == '<'):
    i+=1
    while(S[i] != '>'): # 닫는 태그 나올 때까지 i증가 시키면서 돌기
      i+=1
    i+=1 # 닫는 태그후에 i 증가 시킴

  elif(S[i].isalnum()): # 숫자거나 문자열이면
    start = i
    while(i < len(S) and S[i].isalnum()): # 계속 숫자거나 문자열이면 or 모든 문자열을 다 돌았으면
      i+=1
    tmp = S[start:i] # 해당 구간을 거꾸로 돌린 문자열로 교체
    tmp.reverse()
    S[start:i] = tmp

  else: # 공백이라면 i만 증가시킴
    i += 1

print("".join(S))

깨달은 점

1.  out of range 오류를 피하기 위해서 i < len(word) 도 고려하자
  이터러블한 객체를 끝까지 돌 때의 조건을 나는 지금까지 거의 i != len(lst)로만 사용했다.

하지만, 만약 i를 사용해서 리스트의 값을 비교해야 하는 문이 해당 조건문에 같이 들어가게 된다면 IndexError: list index out of range 의 에러가 나올 수밖에 없다.

그럴 때, i < len(word)를 사용하면, out of range 오류가 나지 않으면서 끝까지 순회하도록 할 수 있다는 것을 알아두자.

 

한 가지 더, 위와 같은 상황에선 i < len(word)를 다른 비교문보다 먼저 작성하자. 조건문 안에서 비교문을 먼저 실행함으로써 똑같이 out of range가 발생할 수 있기 때문!

 

2.  순차적으로 순회하는 이터레이터, i 를 잘 활용하자

  문자열의 길이만큼 반복을 해야 하는 건 알았는데 그것을 for문으로만 계속 한정지어서 생각하다 보니 해결방법이 잘 떠오르지 않았다.
하지만 또 다시 생각해 보면, 이 문제는 for문이나 while문이나 별 차이가 없다.
단지, 반복문의 이터레이터를 얼마나 잘 사용했느냐가 관건인 문제가 아닌가 싶다.


이터러블 객체의 끝까지 순차적으로 순회하는 이터레이터인 i를 조건에 따라서 일정 구간 잘 움직이면서 답을 찾아가는 문제인 것이다.
그래도 for문과 while문을 언제 사용하는 것이 적절한지에 대해서도 알아보자.

 

3.  for문과 while문의 차이점!
  for문 : 구하고자 하는 값이 정확한 조건이 있는 경우, 가독성이 좋다.
  while문 : 구하고자 하는 값이 정확한 조건을 정확히 모를 경우 즉, 유동적인 경우, 가독성은 안좋을 수 있다.

'알고리즘 공부 > coding test' 카테고리의 다른 글

백준 10799 [쇠막대기]  (0) 2022.09.16
백준 1931 [회의실 배정]  (0) 2022.05.20
백준 20115 [에너지 드링크]  (0) 2022.05.20
백준 11399 [ATM]  (0) 2022.05.18
백준 2217[로프]  (0) 2022.05.17
image


https://ko.reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects


const toggleTodo = (id, setTodos) => {
  console.log(id, "할일체크표시");
  setTodos((prev) => {
    return prev.map((item) => {
      if (item.id === id) {
        console.info(item);
        item.done = !item.done;
      }
      return item;
    });
  });
};
...
<CheckCircle onClick={() => toggleTodo(id, setTodos)}>
  {done ? <YesDone /> : <NotYet />}
</CheckCircle>

위의 코드를 보면, 할일 완료버튼을 toggle해주는 함수인 toggleTodo가 있다.

하지만 react 공식문서 내용을 바탕으로 처음 토글 시도에서는 의도대로 setTodos가 한 번만 실행되는 것 같지만, 두번째 토글 시도부터는 의도하지 않았는데 두 번 실행되는 것을 볼 수 있다. (그래서 토글을 해도 토글하지 않아 보인다. -1+1처럼.)

image

그래서 공식문서에 나와있는 해결책대로 strict mode를 해제해주면 위와 같은 이슈는 발생하지 않는다.

 

하지만, 과연 이게 맞는 해결책일까? 의문점이 든다.

  • strict mode를 계속 유지하고 싶은 상황인데 단지 이 이슈때문에 strict mode를 빼는 것인데...
  • strict mode는 부작용을 발견하게 도와주는 모드다. 그렇다면, 현재 내 코드에는 부작용이 생겼기 때문에 좋지 않은 코드인 게 아닐까?

 

`item.done = !item.done` 에서 `item.done = !done`으로 바꿨더니 문제가 바로 해결되었다…!!

const toggleTodo = (id, done, setTodos) => {
  console.log(id, "할일체크표시");

  setTodos((prev) => {
    return prev.map((item) => {
      if (item.id === id) {
        item.done = !done;
      }
      return item;
    });
  });
};

 

왜지?

`item.done = !item.done` 사용할 때

image

react-devtools-backend.js 이란 파일에서도 실행되는데, item.done이 여기서도 바뀌게 된다. true -> false -> true

setTodos((prev) => {
  return prev.map((item) => {
    if (item.id === id) {
      console.log("안녕?");
      console.log("전", item.done);
      item.done = !item.done;
      console.log("후", item.done);
    }
    return item;
  });
});

 

 

`item.done = !done` 사용할 때

image

react-devtools-backend.js 파일에서 실행될 때, item.done이 바뀌게 되지 않는다. true -> false -> false

setTodos((prev) => {
  return prev.map((item) => {
    if (item.id === id) {
      console.log("안녕?");
      console.log("전", item.done);
      item.done = !done;
      console.log("후", item.done);
    }
    return item;
  });
});

 

 

react-devtools는

React로 빌드된 웹 사이트를 디버깅하는 React Developer Tools 브라우저 확장 프로그램이다.

페이지 내 components를 한 눈에 볼 수 있고 react로 개발한다면 용이한 확장 프로그램이다. 

 

 

🤔생각해보자

  • done고정된 값이다! react-devtools-backend.js 에 의해서 백그라운드?에서도 한 번 더 실행될 때 !done 으로 결과는 같게 나오지만,
  • item.done계속 변화되는 값이다! todoItem.js 에서 일단 한번 실행되고, 또 react-devtools-backend.js 에서도 한 번 더 실행되기 때문에 결국은 토글되지 않은 것처럼 보인 것이다.

 

🤗결론

  • react-devtools-backend.js 이 파일이 어떤 코드인지는 잘..모르겠지만.. 함수를 두 번 실행하게 하는 코드가 있는 것 같다. 
  • strict mode를 제거하지 않고 싶은 상황이라면, 고정된 값을 할당함으로써 여러번 함수가 실행되더라도 의도했던 대로 작동할 수 있다.

 

'프로젝트 > 투두리스트' 카테고리의 다른 글

lighthouse 사용해보기!  (1) 2024.04.18
display: grid 사용 이유!  (0) 2024.04.18

문제링크: https://www.acmicpc.net/problem/1931

 

♣ 문제정리

  • 시작시간, 끝나는 시간을 가진 회의들이 있을 때, 회의 수의 최대 개수를 출력하는 문제.
  • 회의가 겹칠 수 없다
  • 회의가 끝나는 동시에 다음 회의가 시작될 수 있다

 

♣ 코드 작성 전 생각할 거리

입력받은 회의들을 시작시간을 기준으로 정렬을 먼저 하기. (회의가 끝났을 때 다음 회의의 시작하는 시간을 바탕으로 계산해야 될 것 같음)

 

첫 회의의 시작시간은 빠를 수록 좋을 줄 알았지만 duration이 길면 이것도 명제가 될 수 없다

(이전 회의 끝나는 시간 <= 다음회의의 시작시간) 의 순서로 하되, 다음 회의의 시작시간이 같은 것들이 여러개라면?

 

start, duration, end 시간을 다 고려를 해야 할 것 같다.

 

===> greedy 기법으로 생각해 본다면, 여러 선택지들이나 두개의 선택지가 있을 때, 어떤 하나를 선택했을 때 빨리 끝나는 회의를 우선적으로 선택하면 되지 않을까?

 

한 가지 더, 끝나는 시간이 같을 경우는? 

빨리 시작하는 회의를 우선적으로 선택해야 한다.

2

2 2

1 2

위 경우 (1 2)를 선택한다면 (2 2)도 선택이 가능해지므로 가능한 회의는 2번으로 결정되지만

(2 2)를 선택한다면 가능한 회의는 1번만으로 결정된다.

 

1. 끝나는 시간을 기준으로 오름차순 정렬

2. 끝나는 시간이 같다면 시작하는 시간이 적은 순으로 오름차순 정렬

 

 

♣ 코드 작성

import sys

input = sys.stdin.readline
n = int(input())

meetings = []
for i in range(n):
    each = list(map(int, input().strip().split(" ")))
    meetings.append(each)

# 끝나는 시간을 기준으로 오름차순 정렬 후 시작시간을 기준으로 오름차순 정렬을 함
meetings = sorted(meetings, key=lambda item: (item[1], item[0])) 

end = meetings[0][1]
cnt = 1

for i in range(1, n):
    if end <= meetings[i][0]: # 다음회의 시작시간이 현재 끝난 시간보다 크거나 같은 것만
        cnt += 1
        end = meetings[i][1]

print(cnt)

전처리를 해주는 과정을 잘 이해하고 코드를 짠다면 뭔가 정말.. 소위 computational thinking으로 쉽게 풀 수 있게 되는 것 같다. 그렇지 않았다면 코드가 복잡해졌을 것 같다.

 

아래 코드는 input() 함수를 사용한 시간이고, 위 코드는 sys.stdin.readline() 을 사용한 시간이다.

 

반복문으로 여러 줄 입력받는 상황에서는 !반드시! 입력은 sys.stdin.readline() 으로 사용해야 겠다!

다만, 한 줄 단위로 입력받기 때문에 개행문자만 제거해주는 strip() 함수만 잘 사용해주자.

'알고리즘 공부 > coding test' 카테고리의 다른 글

백준 10799 [쇠막대기]  (0) 2022.09.16
백준 17413 [단어 뒤집기 2]  (0) 2022.09.16
백준 20115 [에너지 드링크]  (0) 2022.05.20
백준 11399 [ATM]  (0) 2022.05.18
백준 2217[로프]  (0) 2022.05.17

문제링크: https://www.acmicpc.net/problem/20115

 

♣ 문제정리

입력받은 에너지 드링크의 양을 바탕으로 합쳐진 에너지 드링크의 최대양을 구하는 문제

 

♣ 코드 작성 전 생각할 거리

큰 수와 작은 수로 두 수가 주어졌을 때, 최대양을 가지려면 절반의 양을 더해야하는 쪽은 큰 수가 아닌 작은 수가 된다.

더할 양은 최대로 해야하고 반으로 줄여야 하는 것은 가장 적어야 하기 때문이다!

 

결론: 가장 큰 수를 온전히 더하고, 나머지 수는 모두 절반씩 더하면 합쳐진 최대의 에너지 드링크를 출력할 수 있다.

 

♣ 코드 작성

n = int(input())

drink = list(map(int, input().split(' ')))
drink.sort(reverse=True)

sum = drink[0]
for i in range(1, n):
    sum = sum + drink[i]/2

print('%g'%sum)

 

 
 
% (formatting Operator) 앞의 포맷 템플릿에는 대입값 형식이 주어지는데 이를 변환 지시어라고 부른다.
 
-> 포맷을 가진 문자열을 만들어주는 연산자
출처: http://pythonstudy.xyz/python/article/9-%EB%AC%B8%EC%9E%90%EC%97%B4%EA%B3%BC-%EB%B0%94%EC%9D%B4%ED%8A%B8

 

5

3 2 10 9 6

위 테스트 케이스에서 출력값이 20이 나와야 하는데 포맷팅을 안해주면 20.0이 나오기 때문에 '%g'%sum 을 사용.

 

출처: 공식문서, https://docs.python.org/ko/3/library/string.html

%g 는 유효하지 않은 후행 0은 모두 유효숫자부에서 제거되는 특성이 있다. 716.5 와 같이 유효한 후행만 남겨둘 수 있다!! 

 

 

참고: https://coding-nyan.tistory.com/75

 

 

'알고리즘 공부 > coding test' 카테고리의 다른 글

백준 17413 [단어 뒤집기 2]  (0) 2022.09.16
백준 1931 [회의실 배정]  (0) 2022.05.20
백준 11399 [ATM]  (0) 2022.05.18
백준 2217[로프]  (0) 2022.05.17
백준 2358 [평행선]  (0) 2022.02.08

2차 프로젝트 수행 중에 마주친 이슈였다! 

마이페이지 창을 들어가는데 만약 로그인이 안된 상태라면 바로 로그인 창으로 이동하게 useEffect를 사용해서 처음 렌더링될 때 검사하도록 아래처럼 코드를 작성했었다.

하.지.만, useEffect 안의 코드가 두번 실행이 되는 것이다!

useEffect(() => {
    getUserData();
    getUserLogs();

    if (!isLoggedin) {
      alert("로그인을 먼저 해주세용")
      navigate(ROUTES.LOGIN.link, { replace: true });
    }
  }, []);

 

코치님과의 오피스아워 시간으로 strict 모드를 제거하면 두번 실행되는 것이 멈춘다는 것을 알 수 있었다. 하지만 이것은 단지 리액트에서의 버그인 걸로 넘어갔었다.

 

하지만!

 

useEffect(() => { ... }, []) 는 strict 모드 하에서는 두 번 실행되며, 이것은 버그가 아니라 애초부터 의도된 것이다!

https://ko.reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

위의 리액트 공식문서를 본다면 strict 모드에서 react는 예상치 못한 부작용 검사를 위해 아래의 함수라면 의도적으로 이중으로 호출하여 찾을 수 있게 한다는 것을 알 수 있다!

나는 useEffect 안에서 setState 함수를 사용하고 있었기 때문에 그 조건에 부합하였기 때문에 두번 호출해준 것이었다!

 

공식문서 내용을 찬찬히 살펴보다가 facebook의 react 깃헙 레포지토리의 issue까지 접근할 수 있었다.

https://github.com/facebook/react/issues/20090#issuecomment-715927125

그리고 이곳에 모든 내용들이 더 자세하게 적혀져 있는 걸 알 수 있었다!

앞으로는 이렇게 막히는 문제들이 생길 때 공식문서도 보고 구글링도 해야 하지만 이렇게 직접 해당 프레임워크를 개발한 리포의 이슈들에서도 찾아보는 습관을 가져야 겠다!

구글링을 했을 때는 이렇게 고치면 되겠군. 으로 그쳤었지만 이곳에서는 직접 만든 개발자분으로부터 자세하게 해당 이슈에 대해서 설명해주신 것을 볼 수 있어서 신기했다.

 

위 이슈도 다시 찬찬히 읽어보면서 공부 해야겠다.

 

https://github.com/facebook/react

 

GitHub - facebook/react: A declarative, efficient, and flexible JavaScript library for building user interfaces.

A declarative, efficient, and flexible JavaScript library for building user interfaces. - GitHub - facebook/react: A declarative, efficient, and flexible JavaScript library for building user interf...

github.com

 

+ Recent posts