코테 리팩토링) 할인 행사

EBAB!·2024년 5월 16일
0

목표

  • 기존에 문제를 푸는데만 집중해서 풀었던 문제들을 클린 코드, 객체 지향 방식으로 리팩토링
  • 완벽한 효율보다는 다양한 스타일로 작성

문제

코테 문제) https://school.programmers.co.kr/learn/courses/30/lessons/131127

정현이가 원하는 제품을 나타내는 문자열 배열 want와 정현이가 원하는 제품의 수량을 나타내는 정수 배열 number, XYZ 마트에서 할인하는 제품을 나타내는 문자열 배열 discount가 주어졌을 때, 회원등록시 정현이가 원하는 제품을 모두 할인 받을 수 있는 회원등록 날짜의 총 일수를 return 하는 solution 함수를 완성하시오. 가능한 날이 없으면 0을 return 합니다.

제한사항

  • 1 ≤ want의 길이 = number의 길이 ≤ 10
    • 1 ≤ number의 원소 ≤ 10
    • number[i]는 want[i]의 수량을 의미하며, number의 원소의 합은 10입니다.
  • 10 ≤ discount의 길이 ≤ 100,000
  • want와 discount의 원소들은 알파벳 소문자로 이루어진 문자열입니다.
    • 1 ≤ want의 원소의 길이, discount의 원소의 길이 ≤ 12




기존 코드

from collections import defaultdict


DAY_RANGE = 10


def check_list(want_list, dc_list):
    for want_item, cnt in want_list.items():
        if want_item not in dc_list:
            return False
        elif dc_list[want_item] < cnt:
            return False

    return True


def solution(want, number, discount):
    answer = 0
    dc_list = defaultdict(int)
    want_list = {item:cnt for item, cnt in zip(want, number)}
    
    for item in discount[:DAY_RANGE]:
        dc_list[item] += 1
    
    loop_cnt = len(discount) - DAY_RANGE + 1

    for day in range(loop_cnt):
        if check_list(want_list, dc_list):
            answer += 1

        if day + DAY_RANGE < len(discount):
            dc_list[discount[day]] -= 1
            dc_list[discount[day + DAY_RANGE]] += 1

    return answer


수정코드

from collections import Counter

class Mart:
    def __init__(self, discount: list) -> None:
        self.day: int = 0
        self.day_range: int = 10
        self.discounts: list = discount
        self.discounts_counter: Counter = Counter(discount[:self.day_range])

    def shift_discounts_to_next_day(self) -> None:
        if self.is_last_day():
            return
        self._remove_old_discount()
        self._add_new_discount()
        self.day += 1

    def is_last_day(self) -> bool: 
        return self.day + self.day_range == len(self.discounts)

    def _remove_old_discount(self) -> None:
        item_to_remove = self.discounts[self.day]
        self.discounts_counter[item_to_remove] -= 1
        if self.discounts_counter[item_to_remove] == 0:
            del self.discounts_counter[item_to_remove]

    def _add_new_discount(self) -> None:
        item_to_add = self.discounts[self.day + self.day_range]
        self.discounts_counter[item_to_add] += 1

    @property
    def discount_period(self) -> int:
        return len(self.discounts) - self.day_range + 1


class Visitor:
    def __init__(self, want: list, number: list) -> None:
        self.buying_counter: Counter = Counter(dict(zip(want, number)))

    def can_purchase_with_discount(self, discounts_counter: Counter) -> bool:
        for item, required_count in self.buying_counter.items():
            if discounts_counter[item] < required_count:
                return False
        return True


def solution(want: list, number: list, discount: list) -> int:
    answer: int = 0
    visitor: Visitor = Visitor(want, number)
    mart: Mart = Mart(discount)

    for _ in range(mart.discount_period):
        if visitor.can_purchase_with_discount(mart.discounts_counter):
            answer += 1
        mart.shift_discounts_to_next_day()

    return answer

개선점

  • PEP8 가이드라인에 따라 작성된 코드
  • 타입 힌트 추가
  • defaultdict 대신 Counter 사용으로 코드 간소화
  • 객체 지향적 설계를 통해 코드의 재사용성과 유지보수성 증가
    • Mart, Visitor 객체를 통해 역할 분리
  • 메서드 분리와 프로퍼티 사용으로 코드의 모듈성을 높이고 가독성을 개선
    • 맹글링을 통해 내부 사용과 프라이빗 변수에 대해 표현
  • 명확한 변수 및 메서드 명명.
profile
공부!

0개의 댓글