728x90
반응형

1. poetry 설치하기(Mac)

brew install pipx
pipx ensurepath
sudo pipx ensurepath --global #Optional

pipx install poetry
# poetry shell 명령어 사용(가상환경)
poetry self add poetry-plugin-shell

 

2. MongoDB 설치 및 DB 추가(Mac)

brew tap mongodb/brew
brew update
brew install mongodb-community@8.0

mongod --version
# mongodb 서비스를 백그라운드에서 실행
brew services start mongodb-community@8.0

# mongod 실행(mac)
mongod --config /opt/homebrew/etc/mongod.conf --fork


데이터베이스 관련은 mongodb compass 사용 시 손쉽게 설정할 수 있다. 
필자의 경우에는 localhost 27017로 접속하여 books라는 이름의 데이터베이스를 추가했다.


https://www.mongodb.com/ko-kr/products/tools/compass

 

MongoDB Compass

Explore and interact with your data using Compass, the GUI for MongoDB. Query, modify, delete, and more — all from one interface.

www.mongodb.com

 

3. poetry init
이미 프로젝트가 존재하는 경우라면 poetry init 명령어로 pyproject.toml(Poetry 설정 파일) 파일을 생성 가능하다.
없는 경우라면 poetry new poetry-demo 명령어로 프로젝트를 생성

4. package install

poetry install
poetry add scrapy
poetry add pymongo
poetry show

poetry install시 poetry.lock 파일이 생성된다.

5. vscode poetry 가상환경 연동
poetry shell 명령어 실행 시 가상환경에 접속되고 아래와 같이 env path가 출력된다.

Virtual environment already activated: /Users/kimjinho/Library/Caches/pypoetry/virtualenvs/books-scraper-wBYwd1Bg-py3.13

해당 경로를 아래와 같은 방식으로 지정해줘야 한다.
vscode -> command + shift + p -> Python: Select Interpreter -> Enter interpreter path... 


6. scrapy 시작
해당 명령어 실행 시 폴더구조가 아래와 같이 생성된다.

# 스파이더 생성
scrapy startproject scrapy_test
cd scrapy_test
# 스파이더 생성
scrapy genspider test_spider quotes.toscrape.com

.
├── scrapy.cfg
└── scrapy_test
    ├── __init__.py
    ├── items.py
    ├── middlewares.py
    ├── pipelines.py
    ├── settings.py
    └── spiders
        ├── __init__.py
        └── test_spider.py

각 파일을 용도는 다음과 같다.

  • scrapy.cfg: 프로젝트 설정 파일
  • items.py: 데이터 구조 정의
  • middlewares.py: 요청/응답 처리 로직
  • pipelines.py: 데이터 처리 파이프라인
  • settings.py: 프로젝트 설정
  • spiders/: 크롤링 로직이 작성된 스파이더 파일

여기서 spiders 폴더에는 각 웹사이트 별로 파싱 로직을 따로 구현한다.

웹사이트에 기생하고 있는 거미라고 생각하니 딱 크롤러 역할과 맞는 것 같다. 이름을 참 잘 지었다. 

필자는 크롤링한 데이터를 mongodb에 저장하는 것이 목표이기에 해당 파일들 중 middlewares.py는 사용하지 않았다.

생성된 spiders/test_spider.py 파일의 start_urls를 파싱할 url 전체 경로로 지정해 준다.

class TestSpiderSpider(scrapy.Spider):
    name = 'test_spider'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

 

7. scrapy 파싱 시작
items.py에 아래와 같이 파싱할 데이터의 타입을 지정해 준다.

import scrapy

class QuoteItem(scrapy.Item):
    author = scrapy.Field()
    text = scrapy.Field()
    tags = scrapy.Field()

test_spider.py에는 파싱 코드를 추가해 준다.

import scrapy
from ..items import QuoteItem

class TestSpiderSpider(scrapy.Spider):
    name = 'test_spider'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        for quote in response.css('div.quote'):
            item = QuoteItem()

            item['author'] = quote.css('small.author::text').get()
            item['text'] = quote.css('span.text::text').re(r'“(.+)”')[0]
            item['tags'] = quote.css('div.tags a.tag::text').getall()
            print("==============\n", item)

            yield item

 

해당 사이트(http://quotes.toscrape.com/)의 html 구조를 확인해 보면 quote라는 이름의 div에 책 정보들이 
차곡차곡 저장되어 있는 것을 확인할 수 있다. 이런 식으로 패턴을 알았다면 해당 div 안에서 필요한 정보의 css를 각각 확인하면 된다.

div.quote
small.auther

올바르게 css selecter를 지정했다면 scrapy를 실행해 보자
그냥 실행하면 출력을 알아볼 수 없어서 a라는 파일에 출력내용을 저장했다.

scrapy crawl test_spider > a

실행 후 a 파일을 확인해 보면 아래와 같이 파싱한 내용들을 확인할 수 있다. 파싱은 완료되었다.


7. 파싱 결과를 mongodb에 저장
pipelines.py 에 process_item, from_crawler, open_spider, close_spider 메서드를 추가한다.
process_item 메서드만 필수이고 나머지는 선택이다.
여기서 collection 변수는 추가할 collection 이름이다.

  • process_item(self, item, spider):
    각 크롤링된 아이템을 처리함
  • from_crawler(cls, crawler):
    mongo_uri, mongo_db와 같은 설정 정보를 가지는 파이프라인 인스턴스를 생성
  • open_spider(self, spider):
    크롤러가 열릴 때 호출됨
  • close_spider(self, spider):
    크롤러가 닫힐 때 호출됨
import pymongo
import sys
from .items import QuoteItem

class ScrapyTestPipeline:

    collection = 'books'

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
        if not self.mongo_uri: sys.exit("You need to provide a MongoDB Connection String using the -s parameter.")

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        data = dict(QuoteItem(item))
        self.db[self.collection].insert_one(data)
        return item

로직 순서로 보면 다음과 같다. 
1. from_crawler -> __init__ 메서드 순으로 실행되어 초기 설정 정보(mongo_uri, mongo_db)를 저장
2. 크롤러 시작 시 open_spider 메서드가 실행되어 mongodb와 연결됨
3. 각 크롤링 결과를 process_item 메서드에서 mongodb에 추가
4. 크롤러 종료 시 mongodb 연결 끊음

마지막으로 settings.py에 해당 설정을 추가하면 된다.
초기 프로젝트 생성 시 주석으로 추가되어 있다. 여기서 숫자는 우선순위를 의미한다. 낮은 것부터 실행된다.

ITEM_PIPELINES = {
   "scrapy_test.pipelines.ScrapyTestPipeline": 300,
}


아래 명령어로 실행 시 mongodb에 차곡차곡 쌓이는 것을 확인할 수 있다.

scrapy crawl test_spider -s MONGO_URI="mongodb://localhost:27017" -s MONGO_DATABASE="books"

 

참고 URL)

poetry 설치: https://python-poetry.org/docs/

 

Introduction | Documentation | Poetry - Python dependency management and packaging made easy

pipx is used to install Python CLI applications globally while still isolating them in virtual environments. pipx will manage upgrades and uninstalls when used to install Poetry. Install pipx If pipx is not already installed, you can follow any of the opti

python-poetry.org

 

poetry setup: https://python-poetry.org/docs/basic-usage/#project-setup

 

Basic usage | Documentation | Poetry - Python dependency management and packaging made easy

If managing your own virtual environment externally, you do not need to use poetry run since you will, presumably, already have activated that virtual environment and made available the correct python instance. For example, these commands should output the

python-poetry.org

mongodb 설치: https://www.mongodb.com/ko-kr/docs/manual/tutorial/install-mongodb-on-os-x/

 

macOS에 MongoDB Community Edition 설치 - MongoDB 매뉴얼 v8.0

설치 후 macOS가 mongod 실행을 차단할 수 있습니다. mongod을 시작할 때 개발자를 식별하거나 확인할 수 없다는 보안 오류를 수신한 경우 다음을 실행하여 mongod에 액세스 권한을 부여합니다. 시스템

www.mongodb.com

scrapy 사용: https://docs.scrapy.org/en/latest/topics/commands.html

poetry shell 명령어 사용(가상환경): https://www.reddit.com/r/learnpython/comments/1hxftvw/poetry_shell_the_command_shell_does_not_exist/?rdt=56737

 

From the learnpython community on Reddit

Explore this post and more from the learnpython community

www.reddit.com

vscode poetry 가상환경 연동: https://takeheed.tistory.com/33

 

[VSCode] Python 개발 환경 세팅 : Poetry 가상환경

1. Poetry 설치 1.1 Poetry란? Poetry는 pipvenv (pip + virtualenv)와 비슷하게, pip와 virtualenv를 동시에 사용할 수 있게 해주는 packaging과 dependency management를 위한 tool이다. Poetry는 pip와 비교하면 쉽게 이해할 수

takeheed.tistory.com

scrapy pipeline: https://docs.scrapy.org/en/latest/topics/item-pipeline.html

 

728x90
반응형
728x90
반응형

오늘은 프로그래머스에서 진행되는 코테 모의고사 2차 문제를 풀어봤다. 난이도가 조금 쉬운 것 같다.

1차도 풀긴 했는데 시험 종료하면 문제랑 코드를 못 본다는 사실을 몰랐어서 그냥 종료해버렸다ㅜ 

https://career.programmers.co.kr/competitions/2627

 

코딩테스트 실전 대비 모의고사

접수   22년 07월 13일 10:00 ~ 08월 23일 23:59 테스트   22년 07월 13일 10:00 ~ 08월 23일 23:59

career.programmers.co.kr

 

1번

1

설명할 필요도 없을 듯 하다. python의 combinations를 쓰면 조합을 바로 구할 수 있다.

from itertools import combinations

def solution(number):
    return len([s for s in combinations(number, 3) if sum(s) == 0])

 

2번

2

처음에 그냥 리스트를 쪼개고 set 함수를 사용해서 풀었는데 시간 초과가 나왔다

EX) len(set(topping[:i]) == len(set(topping[i:])

딕셔너리를 사용해서 해결했다. 역시 빠르다 우리 사전!

from collections import Counter, defaultdict

def solution(topping):
    front, back = defaultdict(int), dict(Counter(topping))
    ans = 0
    
    for t in topping:
        front[t] += 1
        back[t] -= 1
        if back[t] == 0:
            del back[t]
        if len(front) == len(back):
            ans += 1

    return ans

 

3번

3

문제의 글만 보면 군인들이 목적지로 향하는 상황이지만

우리가 문제를 풀 때는 목적지가 군인들을 찾아간다고 생각하는 것이 풀기 쉽다.

목적지 기준으로 bfs 알고리즘을 사용해서 다른 모든 위치까지의 거리를 구하면 사실상 끝이다.

from collections import deque

def solution(n, roads, sources, destination):
    graph = [[] for _ in range(n+1)]
    ans = []
    
    for s, e in roads:
        graph[s].append(e)
        graph[e].append(s)

    check = [-1 for _ in range(n+1)]
    check[destination] = 0
    q = deque([destination])
    while q:
        i = q.popleft()
        for node in graph[i]:
            if check[node] == -1:
                check[node] = check[i]+1
                q.append(node)

    for s in sources:
        ans.append(check[s])

    return ans
728x90
반응형
728x90
반응형

거리와 탐색 관련된 문제이기 때문에 BFS를 사용해서 풀었다.

 

BFS 풀이 

마지막에 check_li.count(max(check_li))로 한 번에 제일 먼 노드의 개수를 구할 수 있다.

from collections import deque

def solution(N, edge):
    graph = [[] for _ in range(N+1)]
    q = deque([1])
    check_li = [-1]*(N+1)
    check_li[1] = 0
    
    for s, e in edge:
        graph[s].append(e)
        graph[e].append(s)
    
    while q:
        node = q.popleft()
        for n in graph[node]:
            if check_li[n] == -1:
                q.append(n)
                check_li[n] = check_li[node] + 1
    
    return check_li.count(max(check_li))
728x90
반응형
728x90
반응형

간단한 DFS, BFS 문제이다. DFS, BFS를 안 사용하고도 풀어봤다.

 

DFS 풀

def dfs(n, numbers, target, i):
    if i == len(numbers):
        return 1 if n == target else 0
    return dfs(n + numbers[i], numbers, target, i+1) + dfs(n - numbers[i], numbers, target, i+1)

def solution(numbers, target):
    return dfs(0, numbers, target, 0)

생각해보니 굳이 dfs 함수를 만들 필요도 없었다.

아래와 같은 방식으로 numbers 리스트의 앞에 값을 빼가면서, target이 0이 되는지 확인하는 방법도 있다.

def solution(numbers, target):
    if not numbers:
        return 1 if target == 0 else 0
    return solution(numbers[1:], target + numbers[0]) + solution(numbers[1:], target - numbers[0])

 

BFS 풀이

from collections import deque

def solution(numbers, target):
    q = deque([(0, 0)])
    ans = 0

    while q:
        n, i = q.popleft()
        if i == len(numbers):
            if n == target:
                ans += 1
        else:
            q.append([n + numbers[i], i+1])                
            q.append([n - numbers[i], i+1])                
        
    return ans

 

풀이 DFS, BFS 사용 X

def solution(numbers, target):
    res_li = [0]
    for n in numbers:
        li = []
        for res in res_li:
            li.append(res + n)
            li.append(res - n)
        res_li = li
    
    return res_li.count(target)
728x90
반응형
728x90
반응형

맨 앞 스킬부터 순서대로 나오는지 확인해야 되기 때문에, 큐를 사용해서 풀었다.

첫 번째 풀이는 파이썬의 for-else문을 몰라서 check 변수를 사용한 것이다.

두 번째 풀이는 for-else문을 사용했다.

 

풀이 for-else X

1. 스킬 순서를 담는 큐를 만들어준다.

2. 스킬 순서에 포함되는 스킬이 나왔는데 선행 스킬이 아니면 불가능한 스킬트리이므로 check = 0을 해준다.

3. 반대로 선행 스킬이 맞다면 스킬 순서 큐를 popleft 해준다.

4. ans += check(1: 올바른 스킬트리, 0: 불가능한 스킬트리) 

def solution(skill, skill_trees):
    ans = 0
    for skill_tree in skill_trees:
        q = list(skill)
        check = 1
        for s in skill_tree:
            if s in q and s != q.pop(0):
                check = 0
                break
        ans += check
        
    return ans

 

풀이 for-else O

for 문이 break 등으로 중간에 빠져나오지 않고 끝까지 실행됐을 경우 else문이 실행된다.

check 변수가 필요 없어지기에 코드 한 줄이 줄었다.

from collections import deque

def solution(skill, skill_trees):
    ans = 0
    for skill_tree in skill_trees:
        q = deque(skill)
        for s in skill_tree:
            if s in q and s != q.popleft():
                break
        else:
            ans += 1
        
    return ans
728x90
반응형
728x90
반응형

간단한 큐 문제이다. 큐를 안 쓴 풀이도 있다.

이 문제 같이 앞에 값부터 없애가면서 체크해야 되는 문제는 큐를 사용해보도록 하자. 

 

풀이 Q 사용 O

1. 각 progress 별 필요한 요일을 큐에 저장해준다 -> [ceil((100-progresses[i])/speeds[i]) for i in range(len(speeds))]

2. popleft 한 값이 최대값일 경우 ans에  append 1, 아닐 경우 ans 마지막 요소에 1 더함

from math import ceil
from collections import deque

def solution(progresses, speeds):
    ans = []
    q = deque([ceil((100-progresses[i])/speeds[i]) for i in range(len(speeds))])
    max_t = 0
    while q:
        t = q.popleft()
        if t > max_t:
            max_t = t
            ans.append(1)
        else:
            ans[-1] += 1

    return ans

 

풀이 Q 사용 X

from math import ceil

def solution(progresses, speeds):
    ans = []
    li = [ceil((100-progresses[i])/speeds[i]) for i in range(len(speeds))]
    for i in range(len(li)):        
        if ans == [] or max(li[:i]) < li[i]:
            ans.append(1)
        else:
            ans[-1] += 1

    return ans
728x90
반응형
728x90
반응형

단순한 구현 문제이다. dart 딕셔너리를 써서 조건문을 많이 줄일 수 있었다.

 

풀이

def solution(dartResult):
    dart = {'S': 1, 'D': 2, 'T': 3}
    n = ''
    score = []

    for c in dartResult:
        if c.isnumeric():
            n += c
        elif c in dart:
            score.append(int(n) ** dart[c])
            n = ''
        elif c == '*':
            score[-2:] = [s*2 for s in score[-2:]]
        elif c == '#':
            score[-1] = -score[-1]       

    return sum(score)
728x90
반응형
728x90
반응형

비트연산자를 쓸 수 있느냐를 물어보는 문제이다. 처음에는 그냥 bin을 써서 풀었는데,

확실히 비트연산자 쓰는 게 훨씬 편한 것 같다. 

 

풀이 1

def binary(num, n):
    b = bin(num)[2:]
    return (n - len(b))*'0' + b

def str_sum(n, s1, s2):
    s = ''
    for i in range(n):
        s += '#' if s1[i] == '1' or s2[i] == '1' else ' '
    return s

def solution(n, arr1, arr2):
    ans = [0]*n
    for i in range(n):
        ans[i] = str_sum(n, binary(arr1[i], n), binary(arr2[i], n))
    return ans

 

풀이 2 

비트연산자와 replace를 사용하니 훨씬 쉽게 풀린다

def solution(n, arr1, arr2):
    ans = []
    for i in range(n):
        b = bin(arr1[i] | arr2[i])[2:]
        ans.append((n - len(b))*' ' + b.replace('1', '#').replace('0', ' '))
        
    return ans
728x90
반응형
728x90
반응형

그냥 조건문(노가다), DFS, BFS 등의 방식으로 풀 수 있는 문제이다.

나는 BFS 알고리즘을 사용해서 풀었다.

place에 map함수를 쓴 이유는 place가 문자열로 이루어진 리스트이기 때문이다.

예를 들어 place[0]이 문자열이라 place[0][0] = 'X'이 안된다. 그래서 쓰기 쉽게 map함수를 써서 리스트로 바꿔줬다.

 

풀이

1. place 탐색 -> place의 현재 위치가 'P'라면 'X'로 바꾸고 BFS 탐색 시작

2. BFS 탐색)

   상하좌우로 탐색하는데 맨 처음 탐색 시작 위치(i, j)와 다음 위치(Y, X)의 맨헤튼 거리가 2 이하일 경우에만 진행한다.

   다음 위치가 'O' 라면 현재 위치를 'X'로 바꾸고 계속 탐색 진행

   다음 위치가 'P'라면 거리 두기 위반이므로 바로 0 리턴

3. 한 명이라도 위반하면 0을 리턴해야 되므로 BFS 탐색이 끝날 때마다 check &= bfs(i, j) 같은 방식으로 check를 업데이트

4. 작은 반복문 끝날 때마다 ans에 check를 추가  

 
import sys
sys.setrecursionlimit(10000)

def solution(places):    
    def bfs(i, j):
        q = [(i, j)]
        while q:
            y, x = q.pop(0)
            for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                Y, X = y+dy, x+dx
                if 0 <= Y < 5 and 0 <= X < 5 and abs(Y-i) + abs(X-j) <= 2:
                    if place[Y][X] == 'O':
                        place[Y][X] = 'X'
                        q.append((Y, X))
                    elif place[Y][X] == 'P':
                        return 0
        return 1
    
    ans = []
    for place in places:
        place = list(map(list, place))
        check = 1
        for i in range(5):
            for j in range(5):
                if place[i][j] == 'P':
                    place[i][j] = 'X'
                    check &= bfs(i, j)
        ans.append(check)                       
                    
    return ans
728x90
반응형
728x90
반응형

문제에 맞는 자료구조를 생성하고 활용해야 하는 문제이다.

이 문제의 경우에는 Counter를 사용해서 쉽게 풀 수 있다.

주의해야 할 점이 스테이지에 도달한 사람이 없을 때인데 이를 따로 처리해주지 않으면 런타임 에러가 난다.

이거 때문에 조금 푸는데 시간이 걸렸다. 

 

풀이

1. 각 스테이지별 도달한 유저의 수를 담은 딕셔너리를 생성(Counter)

2. 각 스테이지별 실패율 계산

3. 실패율을 기준으로 내림차순 정렬한 리스트의 인덱스를 반환

from collections import Counter

def solution(N, stages):
    ans = []
    n = len(stages)
    counter = Counter(stages)
    for i in range(1, N+1):
        if n:
            ans.append(counter[i] / n)
            n -= counter[i]
        else:
            ans.append(0)
                
    return [i+1 for i in sorted(range(N), key=lambda x : ans[x], reverse=True)]
728x90
반응형
728x90
반응형

문제 상황에 맞는 적절한 자료구조를 생성하고 활용해야 되는 문제다. 

Collections의 Counter를 사용하면 상당히 쉽게 풀 수 있다. 처음에 안 쓰고 풀어서 그런지 쉽지 않았다. 

조합을 직접 구하기는 번거롭기에 itertools의 combinations 함수를 사용했다.

 

풀이 1 (Counter 사용 X)

*cource의 개수 만큼 반복([2, 3]의 경우 -> 2개짜리 조합, 3개짜리 조합 찾기)

1. 모든 order에서 가능한 n(cource 값)개 짜리 조합을 comb_li에 저장

2. comb_li에서 {주문된 개수: [메뉴1, ...], ... }의 형태의 딕셔너리 d를 생성

3. d에서 주문된 개수가 제일 많은 메뉴들을 ans에 추가

from collections import defaultdict
from itertools import combinations

def solution(orders, course):   
    ans = []
    for n in course:
        d = defaultdict(list)
        comb_li = []
        max_cnt = 0        
        for order in orders:
            if len(order) >= n:
                comb_li.extend(list(combinations(sorted(order), n)))
        
        for comb in set(comb_li):            
            cnt = comb_li.count(comb)
            d[cnt].append(''.join(comb))
            if cnt > max_cnt:
                max_cnt = cnt                
        
        if max_cnt > 1:
            ans.extend(d[max_cnt])

    return sorted(ans)

 

풀이 2 (Counter 사용 O)

Counter를 사용했기에 d 딕셔러니가 필요가 없다. d 딕셔너리를 생성하는데 필요했던 자잘한 코드들을 지우니 훨씬

코드가 간결해졌다.

from collections import Counter
from itertools import combinations

def solution(orders, course):   
    ans = []
    for n in course:
        comb_li = []
        for order in orders:
            comb_li.extend(list(combinations(sorted(order), n)))

        counter = Counter(comb_li)
        if counter and max(counter.values()) > 1:
            ans.extend([''.join(k) for k in counter if counter[k] == max(counter.values())])

    return sorted(ans)
728x90
반응형
728x90
반응형

문제 상황에 맞는 적절한 자료구조를 생성하고 활용해야 되는 문제다.

요즘 카카오 초반 문제 특징인 것 같은데, 이 문제의 경우에는 2개의 Dictionary를 사용했다.

python의 defaultdict를 거의 처음 사용해봤는데 꽤 좋은 것 같다.

dict의 메서드인 setdefault도 사용해봤지만 defaultdict 쪽이 조금 더 편한 것 같다.

 

풀이

1. 유저별 신고한 id를 저장하는 report_user_dict, 유저별 신고당한 횟수를 저장하는 report_cnt_dict를 생성한다.

2. 위의 두 사전을 사용해 유저가 신고한 id가 k번 이상인지 확인하고 카운트해준다. 

from collections import defaultdict

def solution(id_list, report, k):
    report_user_dict = defaultdict(set)
    report_cnt_dict = defaultdict(int)
    ans = []
    
    for arg in set(report):
        id, reported_id = arg.split()
        report_user_dict[id].add(reported_id)
        report_cnt_dict[reported_id] += 1
        
    for id in id_list:
        cnt = 0
        for reported_id in report_user_dict[id]:
            if report_cnt_dict[reported_id] >= k:
                cnt += 1 
        ans.append(cnt)
    
    return ans
728x90
반응형
728x90
반응형

간단한 구현 문제이다. 문제에서 정해준 순서대로 입력 문자열을 처리해주면 된다.

 

첫 번째 풀이  

리스트 사용

def solution(new_id):
    # 1
    new_id = new_id.lower()
    
    # 2
    li = [c for c in new_id if c.isalnum() or c in "-_."]
    
    # 3
    i = 0
    while i < len(li)-1:
        if li[i] == li[i+1] == '.':
            li.pop(i)
        else:
            i += 1
    
    # 4
    if li and li[0] == '.':
        li.pop(0)
    if li and li[-1] == '.':
        li.pop()
    
    # 5
    if li == []:
        li.append('a')
    
    # 6
    if len(li) >= 16:
        li = li[:15]
        if li[-1] == '.':
            li.pop()
    
    # 7
    if len(li) <= 2:
        while len(li) < 3:
            li.append(li[-1])
    answer = ''.join(li)
    
    return answer

 

두 번째 풀이

리스트를 생성할 필요가 있을까 싶어서 문자열로도 풀어봤다.

def solution(new_id):
    ans = ""

    # 1
    new_id = new_id.lower()
    
    # 2
    for c in new_id: 
        if c.isalnum() or c in "-_.":
            ans += c
    
    # 3
    while ".." in ans:
        ans = ans.replace("..", '.')
    
    # 4
    if ans and ans[0] == '.':
        ans = ans[1:]
    if ans and ans[-1] == '.':
        ans = ans[:-1]
    
    # 5
    if ans == '':
        ans = 'a'
    
    # 6
    if len(ans) >= 16:
        ans = ans[:15]
        if ans[-1] == '.':
            ans = ans[:-1]
    
    # 7
    while len(ans) < 3:
        ans += ans[-1]
        
    return ans
728x90
반응형
728x90
반응형

문제 상황에 맞는 적절한 자료구조를 생성하고 활용해야 되는 문제다.

카카오 코딩테스트 초반 문제들 특징인 것 같다. 

나름 잘 풀었다고 생각했으나 훨씬 좋은 풀이가 있었기에 두 번째 풀이를 추가했다.

 

풀이 1

record를 따라 유저의 id와 이름을 기록한 딕셔너리인 user_dic, 유저 id와 출력돼야 하는 메시지들이 담긴

리스트인 msg_li를 만들어줬다. 아래와 같은 느낌이다.

msg_li에서 메시지 부분을 유저 id에 매칭 되는 이름으로 수정한 결과를 담은 리스트를 반환해주면 끝이다.

-> [msg.replace(user_id, user_dic[user_id]) for user_id, msg in msg_li]

def solution(records):
    user_dic = {}
    msg_li = []

    for record in records:
        li = record.split()
        if li[0] == "Enter":
            command, user_id, user_name = li
            user_dic[user_id] = user_name
            msg_li.append([user_id, f"{user_id}님이 들어왔습니다."])
        elif li[0] == "Leave":
            command, user_id = li
            msg_li.append([user_id, f"{user_id}님이 나갔습니다."])
        else:
            command, user_id, user_name = li
            user_dic[user_id] = user_name
    
    ans = [msg.replace(user_id, user_dic[user_id]) for user_id, msg in msg_li]
    
    return ans

 

풀이 2

지금 첫 번째 반복문을 보면 Enter와 Change의 경우일 때 중복인 코드가 있는데,

이유는 내가 굳이 필요 없는 자료구조인 msg_li를 생성하고 msg_li를 통해 ans를 완성하려고 했기 때문이다.

msg_li를 생성하지 않고, 그냥 user_dic을 첫 반복문에서 완성하고 두 번째 반복문에서 바로 ans를 완성하면 된다.

위의 방식으로 짠 결과 코드가 훨씬 간단해졌다. printer는 두 번째 반복문에 조건문을 넣기 싫어서 만들었다.

def solution(records):
    user_dic = {}
    printer = {"Enter": "님이 들어왔습니다.", "Leave": "님이 나갔습니다."}
    ans = []
    
    for record in records:
        li = record.split()
        if li[0] in ["Enter", "Change"]:
            user_dic[li[1]] = li[2]

    for record in records:
        li = record.split()
        if li[0] != "Change":
            ans.append(user_dic[li[1]] + printer[li[0]])
            
    return ans
728x90
반응형
728x90
반응형

단순한 구현 문제이다. 

 

풀이

1. 0의 개수, 일치하는 숫자의 개수를 센다.

2. 0의 개수 + 일치하는 숫자 -> 순위가 제일 높은 경우, 일치하는 숫자 -> 순위가 제일 낮은 경우

이므로 이에 맞게 순위를 반환해준다.

def solution(lottos, win_nums):
    zero_cnt = lottos.count(0)
    matching_num_cnt = sum([n in win_nums for n in lottos])
    rank = [6, 6, 5, 4, 3, 2, 1]
    
    return rank[zero_cnt+matching_num_cnt], rank[matching_num_cnt]
728x90
반응형

+ Recent posts