문제

 

20920번: 영단어 암기는 괴로워

첫째 줄에는 영어 지문에 나오는 단어의 개수 $N$과 외울 단어의 길이 기준이 되는 $M$이 공백으로 구분되어 주어진다. ($1 \leq N \leq 100\,000$, $1 \leq M \leq 10$) 둘째 줄부터 $N+1$번째 줄까지 외울 단

www.acmicpc.net

코드

import sys
from functools import cmp_to_key

input = sys.stdin.readline

def cmpDic(a,b):
    if(a[1]<b[1]):
        return 1
    elif (a[1]>b[1]):
        return -1
    if(len(a[0])<len(b[0])):
        return 1
    elif(len(a[0])>len(b[0])):
        return -1
    if a>b:
        return 1
    else:
        return -1

N, M= map(int,input().split())
res={}
for i in range(N):
    a=input().rstrip()
    if(len(a)<M):
        continue
    if(a in res):
        res[a]+=1
    else:
        res[a]=1
res = [(k, v) for k, v in res.items()]

res.sort(key=cmp_to_key(cmpDic))
for i,j in res:
    print(i)

풀이

  • 영단어들을 조건에 맞게 정렬시킨다.
  1. N,M을 입력받는다.
  2. N개의 단어를 받으면서 길이가 M 이상인 단어들만 res에 넣어준다.
    1. res는 딕셔너리를 사용해서 여러번 나온 단어들은 +1씩 해준다.
  3. 입력받은 단어들을 (key, value)의 튜플로 이루어진 리스트로 변환한다.
  4. 변환한 리스트를 조건에 맞게 정렬한다
    1. 자주 나오는 단어일수록 앞에 배치한다.
    2. 해당 단어의 길이가 길수록 앞에 배치한다.
    3. 알파벳 사전 순으로 앞에 있는 단어일수록 앞에 배치한다
  5. 정렬한 res를 출력한다.

firebase 프로젝트 생성

  • firebase 생성의 경우는 해당 글 참고
 

리액트와 파이어베이스 연결하기

파이어베이스 프로젝트 생성 제일 처음 파이어베이스를 연동해주어야 함. Sign in (클릭 시 파이어베이스로 이동) 파이어 베이스 접속 후 로그인. 프로젝트 추가 클릭 프로젝트 이름 입력 애널리

enochkon.tistory.com

리액트에 firebase cli 연결

  • 배포하고 싶은 리액트 프로젝트에 firebase cli 설치
npm install -g firebase-tools
  • firebase cli에 로그인
firebase login

  • 엔터 시 로그인 창 출력
  • 연결하고 싶은 계정을 선택한 후 엑세스 동의를 해주면 cli에 로그인이 완료된다.

계정 연결 창

hosting 서비스를 이용한 배포

  • firebase init
firebase init
  • init 후 배포를 하기 위해 Hosting : Configure files…. 로 내린 후 스페이스바로 선택한 후 엔터를 누른다.

  • 아까 firebase에서 만들어놓은 프로젝트를 선택하기 위해서 “Use an existing project”에서 엔터를 누른다.

  • 처음에 만든 프로젝트를 선택해준다.

  • 배포할 디렉토리를 선택한다.
    기본값은 public, react는 빌드 시 build에 생성되므로 build를 입력해준다.

  • 그 다음은 엔터를 눌러 계속 기본값으로 넘어가다가 “Set up automatic builds and deploy with Github?” 라는 질문에서 ci/cd 구축을 위해 yes를 입력해준다.
  • 그러면 깃허브 로그인 창이 떠오르게 된다.

  • 깃허브 로그인을 성공하면 ci/cd를 연결할 user/repository를 입력한다.

  • 그 후에는 계속 엔터를 쳐주다가 What is the name of the GitHub branch associated with your site's live channel?이라는 질문에서 배포할 브랜치 이름을 적어준다.

  • 그 다음 터미널에 뜬 url을 들어가서 해당 레포의 firebase CLI를 Revoke 해준다.
i  Action required: Visit this URL to revoke authorization for the Firebase CLI GitHub OAuth App:
https://github.com/settings/**********
  • 이렇게 되면 기본적인 설정이 완료되고 .github에 yml파일이 생기고 깃허브 레포에는 github action이 추가된다.

 

이 작업까지 완료하면 수동으로 직접 배포하는 것도 가능하다

npm run build

firebase deploy

 

github action을 사용한 ci/cd 연결

  • 기본적으로 firebase와 github를 연결하게 된다면 두가지의 yml 파일이 생기게 된다.
  1. firebase-hosting-merge.yml
    • 지정한 브랜치가 병합될 때 마다 자동으로 Firebase Hosting으로 배포
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on merge
'on':
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATHTOPET }}'
          channelId: live
          projectId: pathtopet
  1. firebase-hosting-Pull-Request
    • PR을 올릴 때 Firebase Hosting에 대한 미리보기 버전을 자동으로 배포하는 것
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATHTOPET }}'
          projectId: pathtopet

yml파일에 대한 간단한 내용정리

  • name: 워크플로우의 이름
  • 'on': 이 워크플로우가 어떤 GitHub 이벤트에 의해 트리거될지 정의
  • jobs: 워크플로우에서 실행할 작업을 정의
    • if: 조건문으로, 이 작업이 실행될 조건을 지정
      여기서는 pull request가 현재 저장소에서 생성된 것인지 확인.
    • ${{ github.event.pull_request.head.repo.full_name == github.repository }} 이 조건은 pull request가 포크(fork)된 저장소가 아닌 원본 저장소에서 생성됐을 때만 작업을 실행하도록 한다.
    • runs-on: 작업이 실행될 환경을 지정
    • steps: 작업을 구성하는 단계들. 이 단계들은 순차적으로 실행
      • actions/checkout@v4: GitHub Action이 현재 저장소의 코드를 체크아웃하여 작업 실행 환경에 가져온다.
      • FirebaseExtended/action-hosting-deploy@v0: Firebase Hosting으로 배포를 담당하는 GitHub Action
  • 여기서 조금 변경해서 빌드를 자동으로 한 후 그것을 배포하게 코드를 약간 바꿔준다.
name: Deploy to Firebase Hosting on merge

"on":
  push:
    branches:
      - main

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: "21"

      - name: Install Dependencies
        run: npm install 

      - name: Build
        run: npm run build 
        env:
          CI: false

      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: "${{ secrets.GITHUB_TOKEN }}"
          firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATHTOPET }}"
          channelId: live
          projectId: pathtopet
name: Deploy to Firebase Hosting on PR

"on": pull_request

jobs:
  build_and_preview:
    if: "${{ github.event.pull_request.head.repo.full_name == github.repository }}"
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: "21" 

      - name: Install Dependencies
        run: npm install 

      - name: Build
        run: npm run build 
        env:
          CI: false

      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: "${{ secrets.GITHUB_TOKEN }}"
          firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATHTOPET }}"
          projectId: pathtopet
  • 배포되기전 빌드를 자동으로 해주기 위해 node js를 사용해서 npm install 후 build 해준다.

 

이렇게 되면 잘 배포되는것을 확인할 수 있다.

문제

 

프로그래머스

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

programmers.co.kr

 

코드

from functools import cmp_to_key

def cmpFile(a,b):
    if a[0].upper() > b[0].upper():
        return 1
    elif a[0].upper() < b[0].upper():
        return -1
    else:
        if int(a[1]) > int(b[1]):
            return 1
        elif int(a[1]) < int(b[1]):
            return -1
    return 0

def solution(files):
    answer = []
    res=[]
    for i in files:
        head=0
        number=0
        for j in range(len(i)):
            if head==0 and '0'<=i[j]<='9':
                head=j
            if head!=0 and number==0 and ('0'>i[j] or i[j]>'9'):
                number=j
        if(number==0):
            number=len(i)
        answer.append([i[:head],i[head:number],i[number:]])
    answer.sort(key=cmp_to_key(cmpFile))
    
    for head,number,tail in answer:
        res.append(head+number+tail)
    return res

풀이

  • 파일명을 특정 기준을 따라 정렬해라
  1. head, number, tail 구역으로 나눈다.
  2. 나눈 파일명을 기준에 따라 정렬하기 위해 cmpFile함수를 작성하고 cmp_to_key를 사용해서 비교함수로 사용한다
  3. 정렬한 파일명을 다시 합쳐서 리턴한다.

특이사항

  • 람다와 정규식을 사용해서 풀면 이정도로 줄일 수 있단다…
import re

def solution(files):
    a = sorted(files, key=lambda file : int(re.findall('\\d+', file)[0]))
    b = sorted(a, key=lambda file : re.split('\\d+', file.lower())[0])
    return b
  • 이게 파이썬이지…

문제

 

프로그래머스

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

programmers.co.kr

코드

import sys
sys.setrecursionlimit(1000000)

def dfs(land, i, j, m, n, check, patch_id):
    if i < 0 or i >= m or j < 0 or j >= n or check[i][j] != 0 or land[i][j] == 0:
        return 0
    check[i][j] = patch_id 
    size = 1  
    
    size += dfs(land, i + 1, j, m, n, check, patch_id)
    size += dfs(land, i - 1, j, m, n, check, patch_id)
    size += dfs(land, i, j + 1, m, n, check, patch_id)
    size += dfs(land, i, j - 1, m, n, check, patch_id)

    return size

def solution(land):
    m = len(land)
    n = len(land[0])
    check = [[0] * n for _ in range(m)]

    patch_sizes = {}
    patch_id = 1

    for i in range(m):
        for j in range(n):
            if land[i][j] == 1 and check[i][j] == 0:
                patch_size = dfs(land, i, j, m, n, check, patch_id)
                patch_sizes[patch_id] = patch_size
                patch_id += 1

    max_oil = 0
    for j in range(n):
        oil_in_column = set()
        for i in range(m):
            if check[i][j] != 0:
                oil_in_column.add(check[i][j])

        total_oil = sum(patch_sizes[pid] for pid in oil_in_column)
        max_oil = max(max_oil, total_oil)

    return max_oil

풀이

  • 일자로 팠을 때 석유를 최대한 많이 얻는 경우는?
  1. 우선 땅에 있는 모든 석유 웅덩이를 dfs로 확인하여 1,2,3 라벨과 넓이를 붙여준다.
  2. 그 뒤 땅을 한번씩 돌면서 일자로 팠을 때 시추 가능한 양을 체크하면서 최대로 시추 가능한 곳의 양을 max_oil에 저장하고 리턴한다.

특이사항

  • 우선 정확성은 전부 통과했는데 효율성에러 런타임 에러가 나서 왜그런가 하고 생각해봤는데, 파이썬의 재귀 제한 때문에 오류가 나는것을 파악해서 재귀 허용을 늘려주면 통과한다. (기본 재귀 1000번)
  • import sys sys.setrecursionlimit(1000000)
  • 원래는 매 시행때 dfs를 돌면서 계속 체크를 하려고 했다가 그렇게 되면 효율성을 통과하지 못할거라고 생각하고 미리 dfs로 모든 석유의 라벨링을 해주며 진행했다.

콜백 함수란?

  • 다른 코드의 인자로 넘겨주는 함수
  • call 부르다,호출하다 + back 뒤돌아오다, 되돌다 의 합성어
  • → 되돌아 호출해달라.
  • 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임

제어권

호출시점

var intervalId = scope.setInterval(func,delay[, param1,param2,...]);
  • setInterval 함수는 funcdelay를 필수 인자로 받으며 param은 선택적으로 func에서 사용할 매개변수를 전달
var count = 0;
var cbFunc = function () {
    console. log(count);
    if (++count > 4) clearInterval (timer);
}

var timer = setInterval(cbFunc,300);
  • 다음 함수는 cbFunc이라는 함수를 setInterval이라는 다른 코드(함수)의 인자로 넘겨준다 → 콜백함수
  • 이로 인해서 cbFunc를 실행하는 제어권을 setInterval이 가지게 된다
code 호출 주체 제어권
cbFunc(); 사용자 사용자
setInterval(cbFunc,300); setInterval setInterval

콜백함수의 인자

Array.prototype.map(callback[,thisArg])
callback: function(currentValue,index,array)
  • map 매서드는 첫 번째 인자callback 함수를 받고, 생략 가능한 두 번째 인자로 콜백함수 내부에서 this로 인식할 대상을 특정할 수 있다.
  • 두 번째 인자가 생략되면 전역객체가 바인딩.
var newArr2 = [10,20,30].map(function (index, currentValue) {
    console.log(index, currentValue);
    return currentValue + 5;
    });
console.log(newArr2);
//실행결과
// 10 0
// 20 1
// 30 2
// [5, 6, 7]

map 함수의 정의

  • map함수를 사용할 때 index와 currentValue의 위치를 바꿔서 사용하면 역할이 바뀌어 버린다,
    • function (currentValue, index)라고 사용하면 currentValue가 0,1,2 index가 10,20,30이 되어버린다.
  • 즉, 콜백 함수의 제어권을 넘겨받은 코드(map)는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다.

this

  • 별도의 this를 지정하는 방식
Array.prototype.map = function(callback, thisArg) {
  var mappedArr = [];
  for (var i = 0; i < this.length; i++) {
    var mappedValue = callback.call(thisArg || window, this[i], i, this);
    mappedArr[i] = mappedValue;
  }
  return mappedArr;
};
  • 코드를 보았을때 thisArg 입력되지 않으면 전역객체(window)를 아니면 입력된 thisArg의 값을 지정
  • 메서드를 콜백 함수로 전달한 경우에는 단순히 “함수”로서만 호출된다.
var obj = {
  vals: [1, 2, 3],
  logValues: function(v, i) {
    console.log(this, v, i);
  },
};
obj.logValues(1, 2); // { vals: [1, 2, 3], logValues: f } 1 2
[4, 5, 6].forEach(obj.logValues); // Window { ... } 4 0
// Window { ... } 5 1
// Window { ... } 6 2
  • obj의 메서드를 콜백함수로 넘겨도 obj자체가 가는것이 아닌 함수로서만 전달되므로 this가 전역으로 바인딩 된다.

콜백 함수 내부의 this에 다른 값 바인딩 하기

  • 콜백함수 내부에서 this가 객체를 바인딩 하는 여러가지 방법

전통적인 방법

  • 메서드 내부에 this를 저장 후 사용.
var obj1 = {
  name: 'obj1',
  func: function() {
    var self = this;
    return function() {
      console.log(self.name);
    };
  },
};
var callback = obj1.func();
setTimeout(callback, 1000);

→ 실제로는 이렇게 사용하지 않음.

  • 콜백함수 내부에서 this를 사용하지 않는 경우
var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(obj1.name);
  },
};
setTimeout(obj1.func, 1000);

→ 위보단 훨씬 좋지만, 작성한 함수를 this를 이용해 재활용할 수 없게 되어버림

 

  • bind()함수 사용하기
var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(this.name);
  },
};
setTimeout(obj1.func.bind(obj1), 1000);

var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500);

콜백 지옥 벗어나기

  • 동기 : 현재 실행중인 코드가 완료된 후에야 다음코드를 실행하는 방식
    • CPU에 의해 즉시 처리가 가능한 코드 (연산 등의 시간이 많이 걸린다 하더라도)
  • 비동기 : 현재 실행중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어감
    • 별도의 요청, 실행 대기, 보류 등과 관련된 코드

콜백 지옥

예시코드

setTimeout(
  function(name) {
    var coffeeList = name;
    console.log(coffeeList);

    setTimeout(
      function(name) {
        coffeeList += ', ' + name;
        console.log(coffeeList);

        setTimeout(
          function(name) {
            coffeeList += ', ' + name;
            console.log(coffeeList);

            setTimeout(
              function(name) {
                coffeeList += ', ' + name;
                console.log(coffeeList);
              },
              500,
              '카페라떼'
            );
          },
          500,
          '카페모카'
        );
      },
      500,
      '아메리카노'
    );
  },
  500,
  '에스프레소'
);
  • 0.5초 주기마다 커피 목록을 수집하고 출력하는 코드
  • 너무 많은 콜백으로 인해서 들여쓰기가 많아 읽기가 힘들어졌다.

콜백지옥 해결하기

기명 함수 사용하기

var coffeeList = '';

var addEspresso = function(name) {
  coffeeList = name;
  console.log(coffeeList);
  setTimeout(addAmericano, 500, '아메리카노');
};
var addAmericano = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addMocha, 500, '카페모카');
};
var addMocha = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addLatte, 500, '카페라떼');
};
var addLatte = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
};

setTimeout(addEspresso, 500, '에스프레소');
  • 코드의 가독성도 높이고, 순서도 위에서 아래로 내려간다.
  • 하지만 일회성 함수를 전부 변수에 할당하는 것은 비효율적인 일이다.

(ES6) Promise를 이용하기

new Promise(function(resolve) {
  setTimeout(function() {
    var name = '에스프레소';
    console.log(name);
    resolve(name);
  }, 500);
})
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 아메리카노';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 카페모카';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 카페라떼';
        console.log(name);
        resolve(name);
      }, 500);
    });
  });
  • Promise에 인자로 넘겨주는 콜백 함수는 바로 실행되지만, 그 내부에 resolve, reject함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 then 혹은 catch 구문으로 넘어가지 않는다.

(ES6) Generator

var addCoffee = function(prevName, name) {
  setTimeout(function() {
    coffeeMaker.next(prevName ? prevName + ', ' + name : name);
  }, 500);
};
var coffeeGenerator = function*() {
  var espresso = yield addCoffee('', '에스프레소');
  console.log(espresso);
  var americano = yield addCoffee(espresso, '아메리카노');
  console.log(americano);
  var mocha = yield addCoffee(americano, '카페모카');
  console.log(mocha);
  var latte = yield addCoffee(mocha, '카페라떼');
  console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
  • Generator 함수는 next 메서드를 생성해서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다.
  • 그리고 다시 next 메서드를 호출하면 멈췄던 부분에서 시작해서 다음에 등장하는 yield에서 함수실행을 멈춘다.

(ES7) Promise + Async/await

var addCoffee = function(name) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(name);
    }, 500);
  });
};
var coffeeMaker = async function() {
  var coffeeList = '';
  var _addCoffee = async function(name) {
    coffeeList += (coffeeList ? ',' : '') + (await addCoffee(name));
  };
  await _addCoffee('에스프레소');
  console.log(coffeeList);
  await _addCoffee('아메리카노');
  console.log(coffeeList);
  await _addCoffee('카페모카');
  console.log(coffeeList);
  await _addCoffee('카페라떼');
  console.log(coffeeList);
};
coffeeMaker();
  • 비동기 작업을 수행하는 함수를 async의 키워드를 사용해서 생성하고, 비동기 작업이 필요한 위치에만 await를 표기하는 것만으로 비동기 작업 처리

 

기타

  • async/await 지옥이라는 것도 존재

async hell

 

 

[번역] 비동기 지옥(async hell) 탈출방법 — Steemit

비동기(async)는 콜백지옥(callback hell)에서 우리를 자유롭게했습니다. 하지만 이는 잘못된 사용으로 인해 비동기 지옥(async hell)의 탄생으로 이어졌습니다. 이 글에서는 async / await 이 무엇인지 설명

steemit.com

 

문제

 

2304번: 창고 다각형

첫 줄에는 기둥의 개수를 나타내는 정수 N이 주어진다. N은 1 이상 1,000 이하이다. 그 다음 N 개의 줄에는 각 줄에 각 기둥의 왼쪽 면의 위치를 나타내는 정수 L과 높이를 나타내는 정수 H가 한 개의

www.acmicpc.net

코드

N = int(input())

coordinate=[]

for i in range(N):
    coordinate.append(tuple(map(int,input().split())))

max_x = max(coord[0] for coord in coordinate)
max_y = max(coord[1] for coord in coordinate)

grid = [[0 for _ in range(max_x)] for _ in range(max_y)]

for x, y in coordinate:
    for i in range(y):
        grid[i][x - 1] = 1 

grid.reverse()

res=0

for i in range(len(grid)):
    if(grid[i].count(1)==0):
        continue
    if(grid[i].count(1)==1):
        res+=1
        continue
    front_index = grid[i].index(1) + 1
    back_index = len(grid[i]) - grid[i][::-1].index(1) 
    res+=back_index-front_index+1
print(res)

풀이

  • 모든 기둥이 들어가는 최소 다각형 면적을 구해라
  1. 좌표를 입력받아 기둥은 1, 빈공간은 0으로 이차원 배열을 만든다.
  2. 배열을 y좌표 기준 한줄씩 돌면서 면적을 측정한다.
    1. 우선 한 줄에 기둥이 없으면 바로 다음줄로 넘어간다.
    2. 한 줄에 기둥이 한개 있으면 무조건 면적은 1이 추가된다.
    3. 한 줄에 기둥이 여러개 있으면 가장 바깥의 기둥 두개의 면적을 추가한다.

문제

 

22233번: 가희와 키워드

1번째 글을 쓰고 난 후에, 메모장에 있는 키워드는 set, floyd, os가 됩니다. 2번째 글을 쓰고 난 후에, 메모장에 있는 키워드는 set, os가 됩니다. map은 1번째 글과 2번째 글에 중복으로 등장하였음을

www.acmicpc.net

코드

import sys

input = sys.stdin.readline

m, n =map(int,input().rstrip().split())
keyword = {}

unUse=m

for i in range(m):
    keyword[input().rstrip()]=False

for i in range(n):
    memo = input().rstrip().split(',')
    for j in memo:
        if(j in keyword):
            if(not keyword[j]):
                keyword[j]+=True
                unUse-=1
    print(unUse)

풀이

  • 사용되지 않은 키워드의 개수를 구해라
  1. 키워드를 입력받아 딕셔너리 키워드:0 형태로 만들어준다.
  2. unUse , 사용되지 않은 키워드의 개수를 세는 변수는 만들어준다
  3. 블로그에 사용한 키워드를 memo에 입력받고, 언급된 키워드들을 제거해준다.
    1. keyword에 없는 단어들은 체크하지 않는다.
    2. keyword에 있는데 value가 False이 아닌 단어는 이미 한번 이상 사용된 단어이므로 넘어간다.
    3. keyword에 있는 단어인데 value가 False인경우 사용하지 않은 단어이므로 True로 바꿔준 후 unUse-=1을 해준다

들어가며

  • 왜 흐름 제어와 혼잡 제어를 4계층에서 수행해야 하는가?
  • 흐름 제어란 무언인가?
  • 혼잡 제어란 무엇인가?

왜 흐름제어(flow control)와 혼잡제어(congestion control)이 필요할까?

  • 송신 노드와 수신 노드의 용량 및 처리 속도는 정해져 있다.
    • 각 노드의 한계가 있기 때문에 데이터가 너무 많이 전송될 경우 노드가 죽을 가능성이 존재한다.
  • 현실에서 사용하는 네트워크는 비신뢰성을 가지고 있기 때문에.
    • 순서가 바뀌거나 데이터를 잃어 버릴 수 있다.
    • 네트워크에 데이터가 너무 많아 혼잡하거나 수신자가 과부화가 걸릴 수 있다.

흐름 제어 (Flow Control)

  • 송신측과 수신측의 데이터 처리 속도 차이를 해결하기 위한 기법
  • 수신자에게 받는 rwnd(로 제어.흐름 제어가 필요한 이유
  • 수신측의 데이터 처리 속도(or 용량)이 송신측보다 더 빠르면 아무 문제가 없다.
    하지만 송신측의 데이터 처리 속도가 더 빠르다면, 수신측에서 데이터 손실이 발생할 수 있다.
    -> 이를 방지하기 위해서 데이터 흐름을 제어해야 함.

흐름제어에서 속도를 제어하기 위한 두가지 방식

  • Stop and wait
    • 매번 전송한 패킷에 대해 확인 응답을 받아야지만 그 다음 패킷을 전송하는 방법
    • 간단하고 확실한 방법이다.
    • 하지만 앞서 보낸 데이터에 대한 응답이 도착했을 때만 보낼 수 있으므로 비효율적이여서 잘 사용하지 않는다.

  • Sliding Window
    • 수신 측에서 설정한 윈도우 크기만큼 송신측에서 데이터를 전송
    • 윈도우 크기는 수신측의 여유공간을 바탕으로 동적으로 변경됨.Sliding Window

 

혼잡 제어 (Congestion Control)

  • 송신측의 전송 속도와 네트워크의 처리속도의 차이를 해결하기 위한 기법
  • 송신측에서 가지고 있는 cwnd로 제어혼잡 제어가 필요한 이유
  • 현재 네트워크에 너무 많은 데이터가 보내지고 있는 상황에서 송신자가 데이터를 보내려고 하는 상황
    -> 라우터가 너무 많은 데이터를 처리하지 못해 손실이 나며 느려짐
    -> 네트워크 전체가 느려지게 됨.

  • 이 그림에서 만약 빨간 선 쪽에서 데이터 손실이 일어나게 된다면 포함된 라우터들이 느려지면서 다른 모든 데이터 흐름이 같이 느려지게 된다.

혼잡 제어의 방식

  • AIMD 기법
    • Additive Increase Multplicative Decrease
    • cwnd (congestion window)를 기준으로 해결
    • 처음에 패킷을 1MSS(Maximum segment size, 전송할 수 있는 최대 세그먼트 크기)로 전송하다가, 패킷 전송에 실패하게 되면 네트워크가 혼잡하다고 판단하여 보내는 패킷의 양을 반으로 줄임
  • Slow Start
    • 패킷 전송을 1MSS씩 증가시키는것이 아니라 2의 지수승으로 늘린다.
    • 만약 패킷이 손실될 경우 다시 window size를 1 MSS로 줄인다.
    • 임계점 (ssthresh)
      • 임의로 slow start를 여기까지 사용하겠다! 라는 의미
      • 임계점은 패킷 손실이 날 경우 줄어들고, 그렇지 않을 경우 늘어난다. (동적이다)
  • Fast Retransmit
    • 손실 감지 알고리즘으로 ACK Duplicated나 Timeout이 발생하면 본인의 윈도우를 수정
    • 패킷 손실이 발생하면 cwnd를 초기의 윈도우 크기로 재설정
  • Fast Recovery
    • 패킷 손실이 났을 때 3 duplicate ack과 time out으로 구분
    • 중복 상태에서는 수신측으로 전달이 된다는 의미이므로 cwnd를 현재의 반으로 설정
    • time out이 났을 때에는 네트워크가 혼잡하다고 생각하여 cwnd를 초기화
     

  • TCP Tahoe
    • Slow Start, AIMD, Fast Retransmit의 결합

 

  • TCP Reno
    • TCP Taeho에서 Fast Recovery 추가

기준 Flow Control Congestion Control
목적 데이터 송수신 측 간의 효율적인 데이터 전송 관리 전체 네트워크 내에서 데이터 전송의 효율성 및 안정성 유지
사용 이유 수신자의 버퍼 오버플로 방지 네트워크 혼잡 방지
사용되는 메커니즘 수신 측의 버퍼 크기에 기반 (예: TCP의 rwnd) 네트워크의 혼잡 상태에 기반 (예: TCP의 cwnd)
조절 방식 수신 측에서 처리할 수 있는 데이터의 양을 조절 네트워크의 혼잡 상태에 따라 전송 데이터량 조절
주요 알고리즘/기술 TCP의 Sliding Window, GBN TCP의 Slow Start, Fast Recovery

 

기타

  • 만약 cwnd와 rwnd의 크기가 다르면 tcp는 무엇을 기준으로 보낼까?
    -> 둘다 만족하기 위해서 둘중에 작은 것을 기준으로 삼아 통신을 한다.
  • mac에서 window size 확인하기
    sudo tcpdump -i any -n #tcp 패킷을 캡쳐하는 명령
     

-> 여기서 나오는 win이 바로 window size (rwnd, cwnd 실시간으로 네트워크의 상황을 반영해 추론하므로 직접적으로는 표시되지 않는다.)

 

'CS' 카테고리의 다른 글

HTTPS와 SSL Handshake  (0) 2024.01.13
SSL/TLS란?  (1) 2024.01.05

+ Recent posts