프로그래밍-학습기록/코딩테스트

프로그래머스 | python | level 2 | 튜플 | 어거지로 풀고 공부하기

leesche 2020. 12. 21. 17:28
  • 2시간 안 쪽으로 푼 것 같다.

내 최초 풀이

# 튜플
def solution(s):
    answer = []
    given_list_without_braket = list(map(lambda x: x.split(","), s[2:-2].split("},{")))
    number_count_dict = dict()
    for element in given_list_without_braket:
        for number in element:
            if number in number_count_dict:
                number_count_dict[number] = number_count_dict[number] + 1
            else:
                number_count_dict[number] = 1

    for item in sorted(
        list(number_count_dict.items()), key=lambda x: x[1], reverse=True
    ):
        answer.append(int(item[0]))
    return answer

print(solution("{{2},{2,1},{2,1,3},{2,1,3,4}}"))
print(solution("{{1,2,3},{2,1},{1,2,4,3},{2}}"))
print(solution("{{20,111},{111}}"))
print(solution("{{123}}"))
print(solution("{{4,2,3},{3},{2,3,4,1},{2,3}}"))
  • 많이 출현한 숫자 순서대로 튜플이 만들어진다는 것을 몰라서 처음에 많이 혼란스러웠다. 지금도 왜 그런지 모른다. 모든 튜플이 아니라, 이 문제에서 나온 튜플이 이런 성질을 가지고 있다는 것이겠지.
  • 문자열을 파싱한다.
  • 숫자마다 나온 개수를 딕셔너리(숫자 : 나온 개수)에 넣는다.
  • 딕셔너리를 튜플의 리스트로 바꾼다.
  • 리스트를 튜플의 두번째 인자 기준으로 내림차 정렬한다.
  • 리스트의 튜플 첫번째 인자를 순서대로 모아 리스트(답)를 만든다.

다른 사람 풀이

  • Counter, re 모듈을 활용한 풀이, 가장 많이 나온 숫자가 튜플에 우선한다는 원칙으로 풀이

      def solution(s):
    
          s = Counter(re.findall('\d+', s))
          return list(map(int, [k for k, v in sorted(s.items(), key=lambda x: x[1], reverse=True)]))
    
      import re
      from collections import Counter
    • 코드의 짧음을 과시하기라도 하듯 solution을 위로 올려버린 발상도 좋지만, 파이썬 컨벤션에 맞춰 보기 쉽게 바꿔보자.

      • Counter 클래스의 most_common() 메서드를 사용하면 위의 lambda를 활용한 정렬을 생략할 수 있다.

        • 코드는 좀 길어지겠지만 내가 생각하기에 더 읽기 좋아 보이게 리팩토링한다면 다음과 같다.

            import re
            from collections import Counter
          
            def solution(s):
                numbers_sorted_by_frequency = Counter(re.findall("\d+", s)).most_common()
          
                answer = []
                for key, value in numbers_sorted_by_frequency:
                    answer.append(int(key))
          
                return answer
    • 내가 수정한 최적 답안은 다음과 같다.

        import re
        from collections import Counter
      
        def solution(s):
            numbers_sorted_by_frequency = Counter(re.findall("\d+", s)).most_common()
            return [int(key) for key, value in numbers_sorted_by_frequency]
  • lstrip, rstrip 사용해 파싱, 작은 크기의 집합의 원소가 튜플에 우선한다는 원칙으로 풀이

      def solution(s):
          answer = []
    
          s1 = s.lstrip('{').rstrip('}').split('},{')
    
          new_s = []
          for i in s1:
              new_s.append(i.split(','))
    
          new_s.sort(key = len)
    
          for i in new_s:
              for j in range(len(i)):
                  if int(i[j]) not in answer:
                      answer.append(int(i[j]))
    
          return answer
    • 문자열 파싱 방법은 그렇게 놀랄 게 아니다.

    • 정답 리스트에 원소를 넣는 기준이 집합 크기가 작은 것의 요소를 검사하고, 등장한 순서대로 넣는다는 것이다.

    • 의문 → 같은 숫자가 등장하면 answer에 들어가지 않는 것 아닌가?

        print(solution("{{2},{2,3},{2,3,1},{2,3,1,2}}"))
        # [2, 3, 1]
      • 내가 생각하는 튜플 만드는 방식이 틀렸거나, 아니면 프로그래머스 테스트케이스에 중복 원소가 등장하는 경우가 없는 것 같다.

새로 안 것

  • sort(key=len) → 요소의 길이에 따라 정렬함

  • eval() → 쌍따옴표 안에 넣은 것을 바로 실행하여 결과를 출력함. 위험성 때문에 안 쓰는 게 좋다.

  • map의 반환 결과는 iterator, list로 바꿔주어야 일반적으로 사용할 수 있다.

  • re 모듈 → 정규표현식과 그 관련된 함수를 알아두면 문자열 파싱에 매우 유용하다. 근데 보기에 매우 어렵다.

  • collections 의 Counter 모듈...이 아니라 collections 모듈에 Counter 클래스네? 모듈과 클래스의 차이가 뭐지??

    • Counter 클래스(추가 설명) → 어떤 요소가 몇 번 나왔는지 세어서 dict로 만들어준다.
      • 클래스의 메서드로 most_common()이 있는데 가장 많이 나온 순서대로 dict의 item을 정렬해서 리스트로 반환해준다!
  • 문자열 거꾸로 출력할 때 string[::-1]

    • 먼저 두번째 콜론 뒤가 -1인지 알고, 인덱싱할 때 '거꾸로' 탐색한다.

        >>> a
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        >>> a[:3]
        [0, 1, 2]
        >>> a[:2:-1]
        [9, 8, 7, 6, 5, 4, 3]
        >>> a[3:0:-1]
        [3, 2, 1]
        >>> a[3:2:-1] 
        [3]
        >>> a[9:2:-1] 
        [9, 8, 7, 6, 5, 4, 3]