@sleepysoong

[프로그래머스][10번째로 푼 문제] 258712. 가장 많이 받은 선물 (240927) 본문

programming/잘 하는 언어는 없지만 문제는 풀어보고 싶어!

[프로그래머스][10번째로 푼 문제] 258712. 가장 많이 받은 선물 (240927)

sleepysoong 2024. 11. 26. 12:17

구분

코딩테스트 연습 > 2024 KAKAO WINTER INTERNSHIP

채점결과

  • 정확성: 100.0
  • 합계: 100.0 / 100.0

제출 일자

2024년 09월 27일 09:52:51

문제 설명

선물을 직접 전하기 힘들 때 카카오톡 선물하기 기능을 이용해 축하 선물을 보낼 수 있습니다. 당신의 친구들이 이번 달까지 선물을 주고받은 기록을 바탕으로 다음 달에 누가 선물을 많이 받을지 예측하려고 합니다.

  • 두 사람이 선물을 주고받은 기록이 있다면, 이번 달까지 두 사람 사이에 더 많은 선물을 준 사람이 다음 달에 선물을 하나 받습니다.
    • 예를 들어 AB에게 선물을 5번 줬고, BA에게 선물을 3번 줬다면 다음 달엔 AB에게 선물을 하나 받습니다.
  • 두 사람이 선물을 주고받은 기록이 하나도 없거나 주고받은 수가 같다면, 선물 지수가 더 큰 사람이 선물 지수가 더 작은 사람에게 선물을 하나 받습니다.
    • 선물 지수는 이번 달까지 자신이 친구들에게 준 선물의 수에서 받은 선물의 수를 뺀 값입니다.
    • 예를 들어 A가 친구들에게 준 선물이 3개고 받은 선물이 10개라면 A의 선물 지수는 -7입니다. B가 친구들에게 준 선물이 3개고 받은 선물이 2개라면 B의 선물 지수는 1입니다. 만약 AB가 선물을 주고받은 적이 없거나 정확히 같은 수로 선물을 주고받았다면, 다음 달엔 BA에게 선물을 하나 받습니다.
    • 만약 두 사람의 선물 지수도 같다면 다음 달에 선물을 주고받지 않습니다.

위에서 설명한 규칙대로 다음 달에 선물을 주고받을 때, 당신은 선물을 가장 많이 받을 친구가 받을 선물의 수를 알고 싶습니다.

예시

이름 준 선물 받은 선물 선물 지수
joy 0 1 -1
brad 0 1 -1
alessandro 4 1 3
conan 0 1 -1
david 1 1 0

alessandro가 선물을 더 많이 줬던 joy, brad, conan에게서 선물을 3개 받습니다. 선물을 하나씩 주고받은 david보다 선물 지수가 커 선물을 하나 받습니다.
david는 선물을 주고받지 않았던 joy, brad, conan보다 선물 지수가 커 다음 달에 선물을 3개 받습니다.
joy, brad, conan은 선물을 받지 못합니다.

다음 달에 가장 선물을 많이 받는 사람은 alessandro이고 4개의 선물을 받습니다. 따라서 4를 return 해야 합니다.

입출력 예 #3

ab, ac, bc 사이에 서로 선물을 주고받은 수도 같고 세 사람의 선물 지수도 0으로 같아 다음 달엔 아무도 선물을 받지 못합니다. 따라서 0을 return 해야 합니다.

from itertools import combinations

def solution(friends, gifts):
    temp = {}
    point = {}
    result = {}
    for _ in gifts:
        [sender, receiver] = _.split(" ")
        if not sender in temp:
            temp[sender] = {}
        if not receiver in temp[sender]:
            temp[sender][receiver] = 0
        temp[sender][receiver] += 1
        if not sender in point:
            point[sender] = 0
        if not receiver in point:
            point[receiver] = 0
        point[sender] += 1
        point[receiver] -= 1
    def who(a, b):
        try:
            count_a = temp[a][b]
        except:
            count_a = 0
        try:
            count_b = temp[b][a]
        except:
            count_b = 0
        if count_a > count_b:
            return a
        if count_a < count_b:
            return b
        point_a = point[a] if a in point else 0
        point_b = point[b] if b in point else 0
        if point_a > point_b:
            return a
        if point_a < point_b:
            return b
        return None
    for (a, b) in combinations(friends, 2):
        _ = who(a, b)
        if not _ == None:
            if not _ in result:
                result[_] = 0
            result[_] += 1
    return 0 if len(result) < 1 else sorted(result.values())[-1]

느낀 점

for i, friend1 in enumerate(friends):
    for j in range(i+1, len(friends)):
        friend2 = friends[j]
        a = from_count[friend1].count(friend2)
        b = from_count[friend2].count(friend1)
        if a < b:
            next_gift[friend2] += 1
        elif a > b:
            next_gift[friend1] += 1
        elif a == b:
            gi_a = gift_index[friend1]
            gi_b = gift_index[friend2]
            if gi_a > gi_b:
                next_gift[friend1] += 1
            elif gi_a < gi_b:
                next_gift[friend2] += 1

answer = 0
for friend in friends:
    answer = max(answer, next_gift[friend])
return answer

오매 Collectionsdefaultdict라는 것이 있나? 난 Counter 말고는 써본 적이 없는걸 (...)

defaultdict가 무엇인가요?

defaultdict는 기본적으로 값이 존재하지 않는 키에 접근할 때 발생하는 KeyError를 방지하기 위해 설계된 딕셔너리 자료구조입니다. defaultdict는 첫 번째 인자로 제공된 함수나 클래스에 의해 초기화되는 기본 값을 제공합니다.

기본적으로 파이썬의 일반적인 dict는 존재하지 않는 키에 접근하면 KeyError가 발생하지만 defaultdict는 그런 경우에도 기본값을 반환해주는 장점이 있습니다.

from collections import defaultdict

# 기본값으로 int(0)를 갖는 defaultdict
d = defaultdict(int)

# 기본값이 없을 경우 자동으로 0을 할당
d['a'] += 1  # d['a']가 존재하지 않으면 기본값 0에서 1 증가
print(d)  # 출력: defaultdict(<class 'int'="">, {'a': 1})

defaultdict를 사용하는 이유

  • 기본값을 자동으로 설정: 값이 존재하지 않는 키에 접근할 때 기본값을 자동으로 설정해주는 기능은 딕셔너리의 값을 다룰 때 매우 유용합니다.
  • KeyError를 방지: 일반 딕셔너리는 존재하지 않는 키에 접근하면 KeyError가 발생하지만, defaultdict는 미리 지정한 기본값을 반환합니다.

예시: 리스트를 기본값으로 설정

d = defaultdict(list)

# 존재하지 않는 키에 리스트 추가
d['a'].append(1)
print(d)  # 출력: defaultdict(<class 'list'="">, {'a': [1]})

# 여러 값을 리스트에 계속 추가
d['a'].append(2)
print(d)  # 출력: defaultdict(<class 'list'="">, {'a': [1, 2]})

예시: 사용자 정의 초기값 사용

# 초기값으로 5를 반환하는 함수
def default_value():
    return 5

# 기본값으로 함수 결과를 사용하는 defaultdict
d = defaultdict(default_value)

# 존재하지 않는 키 'b'에 접근하면 5가 반환됨
print(d['b'])  # 출력: 5

defaultdict를 사용하면 좋은 경우

  • 카운팅 작업: 리스트나 문자열에서 등장 횟수를 세는 경우. 이때 defaultdict(int)를 사용하면 편리합니다.
  • 그룹화 작업: 데이터를 특정 조건에 따라 그룹화할 때, defaultdict(list)를 사용하면 그룹에 데이터를 쉽게 추가할 수 있습니다.
  • 기본값 설정이 필요한 경우: 딕셔너리에서 초기화 없이 바로 값을 다뤄야 할 때.

defaultdict vs. 일반 dict

defaultdict는 특정 상황에서 유리하지만, 기본적으로는 dict를 사용하는 것이 더 좋습니다. defaultdict는 무조건 기본값을 설정하므로, 원치 않는 키가 생성될 수 있기 때문입니다.

예를 들어, 실수로 존재하지 않는 키에 접근하면 기본값이 설정되어 예기치 않은 데이터가 생성될 수 있습니다.

예시: 사용자 정의 초기값 사용

# 초기값으로 5를 반환하는 함수
def default_value():
    return 5

# 기본값으로 함수 결과를 사용하는 defaultdict
d = defaultdict(default_value)

# 존재하지 않는 키 'b'에 접근하면 5가 반환됨
print(d['b'])  # 출력: 5

defaultdict를 사용하면 좋은 경우

  • 카운팅 작업: 리스트나 문자열에서 등장 횟수를 세는 경우. 이때 defaultdict(int)를 사용하면 편리합니다.
  • 그룹화 작업: 데이터를 특정 조건에 따라 그룹화할 때, defaultdict(list)를 사용하면 그룹에 데이터를 쉽게 추가할 수 있습니다.
  • 기본값 설정이 필요한 경우: 딕셔너리에서 초기화 없이 바로 값을 다뤄야 할 때.

defaultdict vs. 일반 dict

defaultdict는 특정 상황에서 유리하지만, 기본적으로는 dict를 사용하는 것이 더 좋습니다. defaultdict는 무조건 기본값을 설정하므로, 원치 않는 키가 생성될 수 있기 때문입니다.

예를 들어, 실수로 존재하지 않는 키에 접근하면 기본값이 설정되어 예기치 않은 데이터가 생성될 수 있습니다.