사용자 도구

사이트 도구


ps:스타일_가이드

코드 작성 가이드

기본 가이드

    • 두 규칙이 충돌나는 부분이 있다면 Google Python Style Guide를 따른다.
  • 포매팅은 Black을 사용하고 pylint로 체크한다.
    • Google Python Style Guide에는 커스텀 버전 pylintrc 파일을 링크해두고 이것을 사용하라고 하는데, Style guide의 본문과 다르게 인덴트가 2-space로 지정되어 있다 (-_-;). Google의 내부 코딩 스타일 가이드와, 외부 공개용 스타일 가이드와의 차이때문에 꼬인것 같은데.. 외부용 가이드를 따르기로 했으니까.. pylintrc를 4-space로 고쳐서 사용한다
  • [폐지된 과거 가이드 (~2022/10)]

버전

  • BOJ기준으로 지원되는 가장 최신 버전을 쓴다. 2022년 10월 기준으로 v3.10을 쓴다.
  • Pypy로 내야 한다던가 할때는 지원 안되는 문법들을 수정해서 내자. 번거롭지만 빈번하지 않아서 감수할만 하다
  • 파이썬의 새 메이저 버전은 10월경에 릴리즈된다. 보통 BOJ에는 다음해 1월쯤에 업데이트되곤 했다.
  • [폐지된 과거 가이드 (~2022/10)]

제출 언어 - Python or PyPy

  • [장점]
    • 일반적으로 PyPy으로 제출하면 Python보다 빠른 수행시간을 얻을수 있다. Python으로 시간초과가 나는 코드도 PyPy로 제출하여 통과될 수 있다.
  • [단점]
    • PyPy보다는 Python으로 제출된 코드가 훨씬 더 많은데, PyPy로 제출하면 다른 코드들과의 시간 비교가 어렵다.
    • PyPy에서 지원하는 Python 버전은 최신 Python 버전보다 살짝 낮다.
  • [결정]
    • 기본적으로는 Python3으로 제출한다. Python으로 시간초과가 나는 경우에만 PyPy3으로 제출한다.

기본 코드 구조

  • DocString / 메인함수 / 메인함수 호출부분 의 순서로 이루어진다
  • 메인 함수는 ⇒ 입력 / 처리 / 출력의 순서로 처리된다.
    • 의무는 아니다. 한줄을 입력받으면서 바로 처리해서 출력하는 것이 편하다면 그렇게 해도 된다.
  • 입력은
    • 주어지는 모든 값을 읽어서 변수에 그대로 저장하는 것을 기본으로 한다.
      • 그 입력값이 문제 푸는데 필요 없더라도 그냥 입력받는다
        • 변수를 전혀 안쓸 경우에는 #pylint disable 을 써서 린트 경고를 무시한다
      • 입력값을 변환해서 처리해야 하는 경우 (예를 들면 소팅), 변환 작업은 입력단계가 아닌 처리단계에서 하자.
    • 입력 크기에 따라서 input과 sys.stdin.readline 중 한가지를 사용한다.
      • 1000줄 이상이면 sys.stdin.readline, 1000줄 미만이면 input.
    • 입력을 받는 변수명은 문제에서 주어진 변수명을 따른다. 한글자. 대문자도 허용
      • 변수명이 문제에 없으면 적당하게 지어서 쓴다.
      • 튜플로 받아야 할때에는 a_and_b 형태의 입력도 허용한다
    • 입력을 받을때에는 2중 list comprehension을 허용한다. (구글 스타일 가이드에서는 금지되어 있지만 입력부분에서만 예외적으로 허용)
      • matrix = [[int(x) for x in input().split] for _ in range(n)]
        과 같은 용법
    • 기본적인 형태들
      • 정수 N 을 입력받기
        • N = int(input())
      • 정수 N, M 을 입력받기
        • N, M = [int(x) for x in input().split()]
      • 한줄에 주어지는 정수 배열 A를 입력받기
        • A = [int(x) for x in input().split()]
      • N줄로 주어지는 정수 배열 A 를 입력받기
        • A = [int(input()) for _ in range(N)]
      • N줄로 주어지는 정수 배열 A와 B를 입력받기 (i번째 줄에 A[i]와 B[i]가 들어옴)
        • A_and_B = [[int(x) for x in input().split] for _ in range(N)]
          • 가능하면 이쪽을 선호한다.
        • A, B = zip(*([int(x) for x in input().split] for _ in range(N)))
          • 필요에 따라 사용할수 있지만, 더 느리다.
          • 이 형식으로 30만줄을 입력받는 9938번 문제 기준으로 약 200ms 차이.
      • 특정 문자가 나올때까지 계속 입력받기
        • while (line := input()) != 'X':  ...
      • 파일 끝까지 입력받기
        • for line in sys.stdin: ...
  • 처리는 …
  • 출력은 …

입출력

  • 기본적으로는 input()과 print() 을 쓴다.
  • 입력이 크면 (대략 1000줄 이상) 이면 유의미한 속도 저하가 발생하므로 sys.stdin.readline()를 쓴다. 이 경우에 input으로는 통과가 안되는 경우도 많다.
    • input = sys.stdin.readline 이런식으로 단축해서 쓰지 말고, 그냥 우직하게 sys.stdin.readline()을 다 쓴다.
    • 줄수가 많은 것이 아니라, 한줄에 많은 글자가 있는 경우는 input()를 써도 느리지 않다.
    • os.read 등을 쓰면 좀더 빨라지는것 같기도 하지만 굳이 거기까지 할 필요는..
  • 리스트에 담긴 내용을 여러줄에 걸쳐서 출력해야 하는 경우 print(*l, sep='\n') 으로 쓰자.
    • 출력해야할 줄 수가 크고 (10000줄 이상), 출력해야 할 내용이 문자열이라면, 출력할 내용을 스트링으로 만들어서 한번에 출력하자.
      • print('\n'.join(l)) 을 쓴다.
      • 여전히 간단하고, 좀더 빠르다
    • 출력해야할 줄 수가 크더라도, 출력해야 할 내용이 정수라면 그냥 print(*l, sep='\n')를 쓴다.
      • 굳이 쓴다면 print('\n'.join(f'{x}' for x in l)) 를 쓰는 것이 가장 빠르지만, 코드가 복잡해보이고 이것때문에 AC/TLE가 걸리는 경우는 거의 없다.
        • 비슷한 맥락으로 다음과 같은 것들이 있지만, 이것들은 약간 더 느리다
          • print(''.join(f'{x}\n' for x in l))
          • print('\n'.join(str(x) for x in l))
          • sys.stdout.writelines(f'{x}\n' for x in l)
    • 테스트. BOJ 15649번 기준 (아웃풋 약 4만개)
      • 176ms - print(*p), (p가 int 튜플)
      • 152ms - print(*p), (p가 스트링 튜플)
      • 160ms - print(' '.join([str(x) for x in p])) (p가 int 튜플)
      • 92ms - print(' '.join(p)) (p가 스트링 튜플)
    • 성능 테스트를 위해서 더 출력 줄수가 큰 문제가 필요하다면 수 정렬하기 2 으로 테스트하자. 1,000,000 개의 정수를 출력하는 문제이다.
  • sys.stdout.write는 print와 속도 차이가 별로 없으니 신경쓰지 말자.

DocString

  • '''가 아니라 “”“를 쓴다!

파일레벨

  • 일정한 포맷으로 모든 코드에 붙인다.
  • 이런 식으로 쓴다
    • """Solution code for "BOJ 1932. 정수 삼각형".
      
      - Problem link: https://www.acmicpc.net/problem/1932
      - Solution link: http://www.teferi.net/ps/problems/boj/1932
      """

클래스/함수레벨

  • 라이브러리 코드에서는 1-line summary는 필수. 그 이상은 필요하면 쓰는걸로 하자. 1 line summary 에서 빈줄을 한칸 넣은 다음에 작성.
  • 일반 코드에서는 생략해도 상관 없다.
  • 1-line summary를 쓸때는 일반적으로는 클래스는 명사형, 함수는 imperative-style 형태의 동사절로 쓴다
    • imperative-style (”““Fetch rows from a Bigtable.””“) (PEP-257에 호환)
    • descriptive-style (”““Fetches rows from a Bigtable.””“)

Type annotation

  • 라이브러리 함수/클래스에는 필수적으로 붙인다
  • 나머지 경우는 필요할 때에만 붙인다.
  • 구체적 사용법
    • typing모듈과 collections.abc모듈 두 군데에서 모두 가져올수 있는 타입은 collections.abc모듈을 사용
    • 제너레이터에 대해서는 리턴 타입을 Generator[T,None,None] 가 아닌 Iterable[T] Iterator[T]로 써주자
      • https://docs.python.org/3/library/typing.html#typing.Generator 에서는 Iterable과 Iterator 두가지를 다 허용하고 있다. 다른 코드에서 Iterable을 쓰는 경우가 더 많으니 여기도 Iterable로 통일하자
      • Iterable과 Iterator 둘다 가능하지만, 제너레이터에 next를 바로 적용하는 경우가 있는데, 이때 타입체커를 통과시키기 위해서는 Iterator이어야 한다.
    • Union[X,Y] 대신에 X|Y 로 쓴다. Optional[X]도 X|None 로 쓴다

Assignment Expression

  • 매우 편리하다. 사용하자
  • 가끔 BOJ에서 테스트용으로 Pypy로 제출해야 할 경우가 있는데. BOJ의 Pypy 버전은 Python 3.7.4에 대응되어서, Python 3.8부터 도입된 이 기능을 인식 못한다. 그래서 런타임 에러가 나므로 깜빡하지 말자

예외처리

  • 파이썬은 공식적으로 EAFP 스타일을 권장한다. 그것에 따르자
    • 나쁜 코드
      • if "key" in dict_:
            value += dict_["key"]
    • 좋은 코드
      • try:
            value += dict_["key"]
        except KeyError:
            pass

재귀함수

  • 파이썬은 재귀 깊이 제한을 넘어가면 예외가 발생한다. 보통 이 기본값은 1000으로 잡혀있고, 그보다 더 늘리고 싶으면 sys.setrecursion 을 사용할수 있기는 하지만, 실제로 얼마까지 처리가 가능할지는 플랫폼에 따라 다르다.
  • 가급적이면 sys.setrecursion 사용을 자제하자. 이 말은 가급적이면 재귀 대신에 반복문으로 코딩하도록 한다는 의미이다.
  • 상황별 분류
    • DFS / 백트래킹
      • teflib의 DFS 함수는 반복문으로 구현되어 있다. 이것을 그래프 탐색 / 트리 탐색 / 백트래킹(상태공간 트리에서의 탐색) 등에 모두 사용하자
    • top-down DP
      • 코드가 매우 지저분해지거나 비효율적이 되는 상황이 아니라면 bottom-up 으로 처리하자.
    • 분할 정복
      • 데이터를 항상 절반으로 나누는 경우는 뎁스가 로그스케일이 되므로 리밋을 넘기지 않는다. 그래서 그냥 재귀로 구현해도 상관 없다. (예: 머지소트)
      • 한쪽으로 치우쳐져서 나눠질수 있는 경우에는 역시 반복문으로 바꿔서 작성하자
    • 그 외에도 데이터 사이즈 제한이 작아서, 재귀로 구현해도 뎁스가 1000 이하로 들어오는 문제들은, 상황에 따라 재귀로 구현해도 된다,

Inner function

  • 구글 코딩 스타일의 관련 내용은
    • 함수를 encapsulation 하는 용도로는 사용 금지
    • 부모스코프의 변수를 클로징 하는 용도로는 허용
  • 하지만, main 함수 안에 정의된 변수를 인자로 넘기기 싫다고, main 함수 안에 inner function을 만드는 것은 아무리 생각해도 나쁜짓인것 같다.
  • 이렇게 사용하기로 했다.
    • main 함수 안에서는 inner function을 정의하지 않는다.
    • 외부에 함수를 만들고, 번거로워도 인자들을 모두 넘겨주자.
  • 직접 호출이 아니라, 함수 인자로 함수를 넘겨줘야 해서 인자 갯수를 맞춰줘야 하는 경우에는 functools.partial 데코레이터를 사용하자
  • 인자가 엄청 많은 재귀함수 같은 경우.. 외부에 non-rec 함수를 선언하고, 그 안에서 rec 함수를 inner function으로 선언하는것은 허용

libray 코드

  • 라이브러리 코드에서는 속도를 위해서 가독성을 조금 더 포기하는 것을 허용한다.
  • 함수/클래스에 type annotation을 필수적으로 붙인다
  • Docstring을 가급적 붙인다.
    • 첫줄에 요약. 그 아랫줄에 상세를 적는 스타일을 따른다. ⇒ import inliner 가 첫줄만 남기고 지워준다

네이밍 가이드

일반적

  • “Number of foos” 를 표현하는 변수는 foo_count. num_foo 보다 오해의 소지가 적다. foo_cnt 는 단어의 중간 글자를 생략하지 말라는 Google style guide에 위배된다.
  • half-open interval 을 표현하는 변수 이름은 [beg, end) 로 통일한다. 다시 말해, 변수명이 beg, end로 되어 있다면 beg은 포함되고, end는 포함이 안된다는 의미이다.
  • 다른 형태의 범위 표현은 일반적으로 closed interval 를 표현한다. [first, last], [front, back], [left, right]
  • list나 set의 이름은 끝에 s를 붙여서 복수형으로 써준다.
    • 앞에 들어간 변수명이 약자여도 그냥 일반 명사인것처럼 s를 붙여준다
      • index를 ind로 줄여서 썼을때, index의 리스트는 inds 로 쓴다.
      • position을 pos로 줄여서 쓸때, position의 리스트는 poses 로 쓴다
    • set인지 list인지 표기가 필요할때는 xxx_set, xxx_list 로 쓴다
  • ABC를 XYZ로 매핑하는 맵(딕셔너리)의 이름은 abc_to_xyz대신 xyz_by_abc를 사용하자. 변환하는 함수명도 convert_abc_to_xyz 보다는 create_xyz_from_abc 이런식으로.
  • 2차원 좌표계의 좌표를 표현할때
    • 2d 그리드에서 셀의 좌표는 r,c로 표현한다 (r,c)에 해당하는 좌표는 pos 로 표현
    • 2d 좌표평면의 좌표는 x,y 로 쓰자. (x,y)에 해당하는 좌표는 coords 로 표현
    • 좌표평면과 그리드의 구분은, 음수 좌표를 허용하느냐 등의 차이도 있기는 하지만, 기본적으로는 아래로 내려갈때 r 또는 y값이 증가하느냐 감소하느냐의 차이로 구분한다.
  • 그래프에서 경로를 찾을 때, 시작 노드와 끝 노드는 source와 dest로 이름짓자.
    • 기각된 후보들: start/end, from/to, src/tgt, sink, target, …
  • 반댓말 쌍 정리 (일반적인 통용되는 기준이 있는것은 아니지만, 내적 일관성을 위해서)
    • beg/end - half-open interval
    • first/last - closed interval
    • left/right - closed interval
    • source/dest - 탐색의 시작점과 끝점
    • start/finish - 시간의 범위
    • init/target - 상태의 초기값과 마지막값
    • 섞어서 쓰지 말자!! (start/end, start/dest, source/target 등등)
  • 재귀함수의 이름은 뒤에 _rec를 붙이도록 하자. 예) def dfs_rec()
  • 사용되지 않는 값을 저장하기 위해서 변수명으로 _ 를 사용한다
  • 불리언 변수의 네이밍
    • 상태를 표시하는 용도로 사용될때, 기본형은 is_XXX 이다
      • is_reachable, is_visited, …
    • 주어를 같이 써줘야 하는 경우에는 문장형으로 네이밍한다
      • foo_is_enabled (O), is_foo_enabled (X)
    • 행동을 지정하는 용도로 사용될때 (보통은 함수 파라메터로 들어올 경우) 에도 가능하면 상태 표시로 써주자.
      • XX상태이면 YY로 동작해야 하는 경우, use_YY 대신에 is_XX를 쓰는게 좋다는 말..
    • 행동 방식을 상태 표시로 표현하기 힘들 경우에는 imperative-style의 동사절으로 쓰는것 허용.
      • include_top_node (O), should_include_top_node (X), includes_top_node (X)
  • 함수 이름
    • imperative-style의 동사절로 쓰자.
    • 동사는 상황에 따라서 적절히 만들면 되지만, 딱 떠오르는게 없으면 대충 아래에서 고르자
      • 리턴값을 비용이 거의 안드는 방식으로 찾을수 있으면 (단순 테이블룩업 등), get_foo()
      • 주어진 범위 안에서 답을 찾아서 리턴하는 경우, find_foo()
      • 주어진 파라메터를 가지고 계산해서 답을 구해서 리턴하는 경우, compute_foo()
        • calc_foo 도 흔히 쓰이지만 compute로 통일하자
      • 주어진 파라메터를 가지고 복잡한 구조의 데이터를 만들어 리턴하는 경우, create_foo()
      • 불리언 값을 리턴하는 함수는 is_foo()

PS 특화

  • 문제를 풀기 위한 함수를 main 밖으로 빼야 할 경우, 그 함수의 이름은 간단히 solve()로 쓰자.
    • 입력 처리 로직과 풀이 로직을 분리할 때, 풀이 로직을 재귀함수로 짜야 할때 등등
  • 문제의 답을 저장하는 변수는 간단히 answer 로 쓰자.
    • 프로그래머스에서 기본적으로 쓰이는 변수명. 기각된 후보들: ans, result, res, …
  • 특정 자료구조를 표현하는 변수는 자료구조 이름으로 표현하는 것이 간편하다
    • stack ⇒ stack or st
    • queue ⇒ queue or q
    • deque ⇒ deq
    • graph ⇒ graph
    • tree ⇒ tree
    • heap ⇒ heap
    • disjoint set ⇒ dsu
    • fenwick tree ⇒ fenwick
    • segment tree ⇒ segtree
    • order statistic tree ⇒ ost
  • dp 결과를 저장하는 배열이나 테이블 이름은 dp로 하자
    • 2차원 이상의 dp에서, 가장 최근의 행만 저장해도 충분한 경우가 있다. 예를 들어 dp[n][i] = f(dp[n-1][i], dp[n-1][i-1]) 이면 최근 두 행에 대한 배열 두개만 필요하다. 이럴때는 dp_cur, dp_prev로 한다.
  • 그래프에서는 대부분
    • for v in graph[u] 의 꼴로 변수명을 쓸것이다
    • 드물게 for successors in graph: 의 형태로 변수명을 쓸 때가 있다
  • 문제의 입력값을 저장할 때에는, 문제 설명에서 제시되는 변수명을 그대로 쓴다. 대문자인 경우에는 그냥 대문자로 쓴다.
    • 다른 스타일 가이드의 원칙과 위배되기는 한다..
    • 그러나 다른 의미있는 이름으로 변수명을 새로 만드는것 보다, 그냥 문제에 주어진 이름을 쓰는게 가장 직관적이고 이해하기 쉽다.
    • 대문자를 썼다고 상수로 오해받을 여지도 적다
  • 입력받는 값은 모두 변수에 저장한다. 풀이에서 실제로 사용되지 않는 값, 또는 따로 변수에 저장하지 않고 바로 처리가 가능한 값도, 그냥 우선 변수에 저장해놓고 안쓰든가 다른 처리를 하든가 한다. 그게 덜 헷갈린다.
    • 안쓰는 변수는 옆에 # pylint: disable=unused-variable 를 적어서 lint warning을 서프레스하자.

기타

  • LeetCode에서는 가이드에서와는 달리 메소드 이름으로 snake_case가 아닌 camelCase를 사용한다. LeetCode에 작성할 때는 일관성을 위해 새로운 메소드도 camelCase로 만든다.

스타일 가이드에 있지만 잘 까먹는 것들

  • import는 패키지와 모듈에만 사용. 클래스나 함수단위로 import하지 않음.
    • 코드가 길어지고 속도도 살짝 더 느리지만 큰 차이 아니니 이렇게 하자.
  • from XX import YY 로 임포트한다고 해서 그냥 import 하는 라인들과 따로 그룹으로 묶지 않는다
    • 이런식으로 같은 그룹으로 묶고 같이 정렬해서 import 한다
    • from collection.abc import Iterable
      import sys
      from typing import TypeVar

기타 코딩 스타일

  • 리스트에 += 보다 append와 extend를 사용 (약간 더 빠르다)
    • l += [x] 대신 l.append(x)
    • l += [x, y] 대신 l.extend([x,y])
  • 튜플 or 리스트를 소팅할때, n번째 원소를 기준으로 할때는, 키로 lambda가 아닌 operator.itemgetter을 쓰자.
    • 좀 더 명확하고 (이건 의견이 갈릴수도..) 약간 더 빠르다. 그리고 파이썬 공식 문서에서 추천하는 방법이다.
    • 비슷하게, namedTuple를 소팅할때는 operator.attrgetter를 사용
  • 타입 체킹은 isinstance(var, some_type) 으로 한다 type(var) is some_type 보다 약간 빠르고, 상속된 타입을 체크할 수 있는 장점이 있다.
  • 4-space 인덴트를 넣은 블럭이 윗줄과 구분이 잘 안될때, 인덴트를 추가한다
    • 이런상황이다
      • if (condition1 
            and condition2):
            statement
    • PEP8에서는 그냥 두기, 커멘트 넣기, 인덴트 추가하기 등의 해결책을 소개하지만 특정한 방식을 추천하지는 않는다
    • Flake8 은 이것을 anti-pattern으로 간주하고 에러를 낸다. 인덴트 추가하기를 best practice로 소개한다
    • yapf에서도 인덴트를 추가하는 식으로 처리한다.
  • 줄바꿈은 이항연산자 뒤에서 한다.
    • PEP8에서는 새로운 코드들은 이항연산자의 에서 줄바꿈하기를 권장한다.
    • 나도 이항연산자의 에서 줄바꿈하는 것이 더 보기 좋다는 데에 동의한다.
    • 하지만 YAPF는 이항연산자의 에서 줄바꿈을 강제한다. 그냥 그렇게 두자..
  • 함수가 예외적인 상황에 놓였을 때에는 (위상정렬을 해야 하는데 주어진 그래프에 사이클이 있다든가..) return None이 아니라, 예외를 명시적으로 raise하도록 하자
    • 예외를 따로 만들 필요는 없다. built-in중에서 선택하자. (아마도 대부분 ValueError)
  • 리스트에서 인접한 원소 두개씩을 갖고 뭘 해야 하는 경우
    • (1) for i in range(1, len(l)): x, y = l[i-1], l[i]
      • ⇒ 기본적이지만, 안 예쁘다
    • (2) for x, y in zip(l, l[1:]):
      • ⇒ 예쁘지만, 리스트를 복사해서 새로 만들어야 해서 비효율적
    • (3) iterable, copied = itertools.tee(lst); next(copied); for x, y in zip(iterable, copied):
      • ⇒ 가장 효율적이지만 길다.
        • python 3.10부터 추가되는 pairwise도 이런식으로 구현될듯
    • (4) prev = l[0]; [-prev + (prev := x) for x in l[1:]]
      • 신박한데.. 제약이 있어서 못쓸수도 있다.
    • =⇒ 상황에 따라서 (2)나 (3)을 섞어 쓰자. 물론 python 3.10이 나오면 pairwise로 갈아탄다.
  • 전역 변수를 로컬 스코프에 다시 선언하는 최적화
    • 한줄 요약: 마이크로 최적화는 하지 않는다. 이것도 마찬가지.
    • 파이썬에서는 로컬 스코프를 접근하는 것이 전역 스코프에 접근하는 것보다 빠르기 때문에, 최적화를 위해서 똑같은 변수/함수를 로컬 스코프에 다시 선언하는 경우가 있다.
    • 이것은 실행속도를 살짝 줄여주기는 한다. 19580 문제는 최대 100만번의 sys.stdin.readline() 호출과, 최대 50만번의 heapq.heappop(), heapq.heappush() 호출이 필요한 문제이다.
      • 이 문제를 그냥 sys와 heapq를 import 하고 sys.stdin.readline()와 heapq.heappop()를 호출하도록 구현한 것을 베이스라인으로 두었다.
      • 이제 from heapq import heappop 으로 임포트하고, rl=sys.stdin.readline 을 글로벌에 선언해서 rl()과 heappop()로 호출하자 100ms 정도가 단축되었다.
      • rl = sys.stdin.readline 와 hpop = heapq.heappop 을 로컬에 선언해서 rl()과 hpop()으로 호출하자 다시 50ms 정도가 단축되었다..
      • 로컬스코프과 글로벌스코프에서의 차이는 총 200만번정도를 호출해도 50ms정도밖에 차이를 안내주는것 같다
    • 하지만 실행 속도 단축은 미미하다. 사용하지 말자.
  • 구조화된 클래스를 사용할때에는 dataclass를 쓰자
    • 선택지로는 collections.namedtuple, typing.namedtuple, dataclass, 그냥 dict, 그냥 클래스, 등등이 있다.
    • 불가능한 경우를 제외하고는 dataclass에 slots를 켜서 사용하는 것을 기본으로 하자. 해셔블해야 하다면 frozen을 켜면 된다.
      • “You should avoid collections.namedtuple unless you specifically want to define a tuple”

기타 코딩 스타일 (PS 특화)

  • '무한대'는 float('inf')를 사용한다.
    • 상수로 정의해서 쓰자.
      • INF = float('inf')
    • 임의의 큰 값, 예를 들면 987654321이나 1e9 보다 의미가 분명하다.
    • math.inf도 동일하지만, math 모듈을 import 해야 하는 것이 불편하다.
  • 결과값을 얼마로 나눈 나머지를 출력하게 하는 문제에서의 '얼마'는 상수로 정의해서 쓴다.
    • MOD = 1_000_000_007
  • 필요한 경우 main()함수의 첫줄에 sys.setrecursionlimit(10**9) 를 적는다.
    • 기본값은 1000이므로, 재귀로 풀어야 하는 웬만한 문제에서는 저 값을 새로 세팅해야 한다. 문제마다 다른 값을 적용할 필요 없이 항상 일괄적으로 10**9로 적도록 하자.

Idiom

  • count_if : Iterable에서 condition을 만족하는 원소의 갯수
    • 가능한 후보들
      • 1) len([x for x in iterable if cond(x)])
      • 2) sum(1 for x in iterable if cond(x))
      • 3) sum(cond(x) for x in iterable)
    • 직관적인것은 1번>2번>3번 순이다. 하지만 1번은 리스트를 전부 만들어야 하니 효율성이 좀 떨어질것 같고. 2번이나 3번은 사실 둘다 비슷한데, 2번을 쓰는 코드를 더 많이 접했던 기억이고 더 익숙하다. 그리고 2번이 3번보다 좀더 빠르다
    • 그러므로 2번 방식으로 쓰자.
  • find_first : condition을 만족하는 첫번째 원소
    • next(x for x in iterable if cond(x))
  • Chunking data into equal-sized groups
  • Number of element in iterable
    • count_if에서 condition이 따로 없는 경우이기는 하다
    • 가능한 후보들
      • 1) len(list(iterable))
      • 2)
        counter = itertools.count()
        deque(zip(iterable, counter), maxlen=0)
        return next(counter)
      • 3) sum(1 for _ in iterable)
    • 여기 에 따르면, 놀랍게도 리스트를 새로 만드는 1번이 가장 빠르다고 한다. 그러나 메모리 소비가 클것으로 예측된다. 메모리가 절약될것으로 생각되는 2번과 3번은 2번이 빠르다고 한다. 요약하면 아래와 같은데..
      • 1) 속도 빠름 / 코드 짧음 / 메모리 많음
      • 2) 속도 빠름 / 코드 복잡 / 메모리 적음
      • 3) 속도 덜빠름 / 코드 짧음 / 메모리 적음
    • 2번은 코드가 너무 길어서 탈락. 속도의 1번이냐 메모리의 3번이냐인데, 속도 차이가 그렇게 크지 않을것이라 생각해서 3번을 쓰기로 했다. count_if 의 idiom과도 일관성이 있다는 것도 장점,

토론

댓글을 입력하세요:
J S K N Q
 
ps/스타일_가이드.txt · 마지막으로 수정됨: 2023/08/20 15:49 저자 teferi