"itertools" --- 효율적인 루핑을 위한 이터레이터를 만드는 함수
*************************************************************

======================================================================

이 모듈은 APL, Haskell 및 SML의 구성물들에서 영감을 얻은 여러 *이터레
이터* 빌딩 블록을 구현합니다. 각각을 파이썬에 적합한 형태로 개선했습니
다.

이 모듈은 자체적으로 혹은 조합하여 유용한 빠르고 메모리 효율적인 도구
의 핵심 집합을 표준화합니다. 함께 모여, 순수 파이썬에서 간결하고 효율
적으로 특수화된 도구를 구성할 수 있도록 하는 "이터레이터 대수(iterator
algebra)"를 형성합니다.

예를 들어, SML은 테이블 화 도구를 제공합니다: 시퀀스 "f(0), f(1), ..."
를 생성하는 "tabulate(f)". "map()"과 "count()"를 결합하여 "map(f,
count())"를 형성해서 파이썬에서도 같은 효과를 얻을 수 있습니다.

이러한 도구와 그들의 내장 대응물들은 "operator" 모듈의 고속 함수와도
잘 작동합니다. 예를 들어, 곱셈 연산자는 두 벡터에 걸쳐 map 되어 효율적
인 내적(dot-product)을 형성할 수 있습니다: "sum(map(operator.mul,
vector1, vector2))".

**무한 이터레이터:**

+--------------------+-------------------+---------------------------------------------------+-------------------------------------------+
| 이터레이터         | 인자              | 결과                                              | 예                                        |
|====================|===================|===================================================|===========================================|
| "count()"          | start, [step]     | start, start+step, start+2*step, ...              | "count(10) --> 10 11 12 13 14 ..."        |
+--------------------+-------------------+---------------------------------------------------+-------------------------------------------+
| "cycle()"          | p                 | p0, p1, ... plast, p0, p1, ...                    | "cycle('ABCD') --> A B C D A B C D ..."   |
+--------------------+-------------------+---------------------------------------------------+-------------------------------------------+
| "repeat()"         | elem [,n]         | elem, elem, elem, ... 끝없이 또는 최대 n 번       | "repeat(10, 3) --> 10 10 10"              |
+--------------------+-------------------+---------------------------------------------------+-------------------------------------------+

**가장 짧은 입력 시퀀스에서 종료되는 이터레이터:**

+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| 이터레이터                   | 인자                         | 결과                                              | 예                                                            |
|==============================|==============================|===================================================|===============================================================|
| "accumulate()"               | p [,func]                    | p0, p0+p1, p0+p1+p2, ...                          | "accumulate([1,2,3,4,5]) --> 1 3 6 10 15"                     |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "chain()"                    | p, q, ...                    | p0, p1, ... plast, q0, q1, ...                    | "chain('ABC', 'DEF') --> A B C D E F"                         |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "chain.from_iterable()"      | iterable                     | p0, p1, ... plast, q0, q1, ...                    | "chain.from_iterable(['ABC', 'DEF']) --> A B C D E F"         |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "compress()"                 | data, selectors              | (d[0] if s[0]), (d[1] if s[1]), ...               | "compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F"               |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "dropwhile()"                | pred, seq                    | seq[n], seq[n+1], pred가 실패할 때 시작           | "dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1"             |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "filterfalse()"              | pred, seq                    | pred(elem)이 거짓인 seq의 요소들                  | "filterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8"         |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "groupby()"                  | iterable[, key]              | key(v)의 값으로 그룹화된 서브 이터레이터들        |                                                               |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "islice()"                   | seq, [start,] stop [, step]  | seq[start:stop:step]의 요소들                     | "islice('ABCDEFG', 2, None) --> C D E F G"                    |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "starmap()"                  | func, seq                    | func(*seq[0]), func(*seq[1]), ...                 | "starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000"          |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "takewhile()"                | pred, seq                    | seq[0], seq[1], pred가 실패할 때까지              | "takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4"               |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "tee()"                      | it, n                        | it1, it2, ... itn 하나의 이터레이터를 n개의 이터  |                                                               |
|                              |                              | 레이터로 나눕니다                                 |                                                               |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+
| "zip_longest()"              | p, q, ...                    | (p[0], q[0]), (p[1], q[1]), ...                   | "zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-"    |
+------------------------------+------------------------------+---------------------------------------------------+---------------------------------------------------------------+

**조합형 이터레이터:**

+------------------------------------------------+----------------------+---------------------------------------------------------------+
| 이터레이터                                     | 인자                 | 결과                                                          |
|================================================|======================|===============================================================|
| "product()"                                    | p, q, ... [repeat=1] | 데카르트 곱(cartesian product), 중첩된 for 루프와 동등합니다  |
+------------------------------------------------+----------------------+---------------------------------------------------------------+
| "permutations()"                               | p[, r]               | r-길이 튜플들, 모든 가능한 순서, 반복되는 요소 없음           |
+------------------------------------------------+----------------------+---------------------------------------------------------------+
| "combinations()"                               | p, r                 | r-길이 튜플들, 정렬된 순서, 반복되는 요소 없음                |
+------------------------------------------------+----------------------+---------------------------------------------------------------+
| "combinations_with_replacement()"              | p, r                 | r-길이 튜플들, 정렬된 순서, 반복되는 요소 있음                |
+------------------------------------------------+----------------------+---------------------------------------------------------------+

+------------------------------------------------+---------------------------------------------------------------+
| 예                                             | 결과                                                          |
|================================================|===============================================================|
| "product('ABCD', repeat=2)"                    | "AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD"             |
+------------------------------------------------+---------------------------------------------------------------+
| "permutations('ABCD', 2)"                      | "AB AC AD BA BC BD CA CB CD DA DB DC"                         |
+------------------------------------------------+---------------------------------------------------------------+
| "combinations('ABCD', 2)"                      | "AB AC AD BC BD CD"                                           |
+------------------------------------------------+---------------------------------------------------------------+
| "combinations_with_replacement('ABCD', 2)"     | "AA AB AC AD BB BC BD CC CD DD"                               |
+------------------------------------------------+---------------------------------------------------------------+


이터레이터 도구 함수
====================

다음 모듈 함수는 모두 이터레이터를 생성하고 반환합니다. 일부는 길이가
무한한 스트림을 제공해서, 스트림을 자르는 함수나 루프로만 액세스해야
합니다.

itertools.accumulate(iterable[, func, *, initial=None])

   누적 합계나 다른 이항 함수(선택적 *func* 인자를 통해 지정됩니다)의
   누적 결과를 반환하는 이터레이터를 만듭니다.

   *func*가 제공되면, 두 인자를 취하는 함수여야 합니다. 입력
   *iterable*의 요소는 *func*에 대한 인자로 허용될 수 있는 모든 형일
   수 있습니다. (예를 들어, 기본 더하기 연산에서 요소는 "Decimal"이나
   "Fraction"을 포함하는 모든 더할 수 있는 형일 수 있습니다.)

   일반적으로, 출력되는 요소 수는 입력 iterable과 일치합니다. 그러나,
   키워드 인자 *initial*이 제공되면, 누적이 *initial* 값으로 시작하여
   출력에 입력 iterable보다 하나 많은 요소가 있게 됩니다.

   대략 다음과 동등합니다:

      def accumulate(iterable, func=operator.add, *, initial=None):
          'Return running totals'
          # accumulate([1,2,3,4,5]) --> 1 3 6 10 15
          # accumulate([1,2,3,4,5], initial=100) --> 100 101 103 106 110 115
          # accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
          it = iter(iterable)
          total = initial
          if initial is None:
              try:
                  total = next(it)
              except StopIteration:
                  return
          yield total
          for element in it:
              total = func(total, element)
              yield total

   *func* 인자는 여러 가지 용도가 있습니다. 누적 최솟값을 위해서는
   "min()", 누적 최댓값을 위해서는 "max()", 누적 곱을 위해서는
   "operator.mul()"로 설정할 수 있습니다. 할부 상환 표는 이자를 누적하
   고 지불을 적용하여 만들 수 있습니다. 일차 점화식은 iterable에 초깃
   값을 제공하고 *func* 인자에서 누적 합계만 사용하여 모델링 할 수 있
   습니다:

      >>> data = [3, 4, 6, 2, 1, 9, 0, 7, 5, 8]
      >>> list(accumulate(data, operator.mul))     # running product
      [3, 12, 72, 144, 144, 1296, 0, 0, 0, 0]
      >>> list(accumulate(data, max))              # running maximum
      [3, 4, 6, 6, 6, 9, 9, 9, 9, 9]

      # Amortize a 5% loan of 1000 with 4 annual payments of 90
      >>> cashflows = [1000, -90, -90, -90, -90]
      >>> list(accumulate(cashflows, lambda bal, pmt: bal*1.05 + pmt))
      [1000, 960.0, 918.0, 873.9000000000001, 827.5950000000001]

      # Chaotic recurrence relation https://en.wikipedia.org/wiki/Logistic_map
      >>> logistic_map = lambda x, _:  r * x * (1 - x)
      >>> r = 3.8
      >>> x0 = 0.4
      >>> inputs = repeat(x0, 36)     # only the initial value is used
      >>> [format(x, '.2f') for x in accumulate(inputs, logistic_map)]
      ['0.40', '0.91', '0.30', '0.81', '0.60', '0.92', '0.29', '0.79', '0.63',
       '0.88', '0.39', '0.90', '0.33', '0.84', '0.52', '0.95', '0.18', '0.57',
       '0.93', '0.25', '0.71', '0.79', '0.63', '0.88', '0.39', '0.91', '0.32',
       '0.83', '0.54', '0.95', '0.20', '0.60', '0.91', '0.30', '0.80', '0.60']

   최종 누적값만 반환하는 유사한 함수에 대해서는 "functools.reduce()"
   를 참조하십시오.

   버전 3.2에 추가.

   버전 3.3에서 변경: 선택적 *func* 매개 변수를 추가했습니다.

   버전 3.8에서 변경: 선택적 *initial* 매개 변수를 추가했습니다.

itertools.chain(*iterables)

   첫 번째 이터러블에서 소진될 때까지 요소를 반환한 다음 이터러블로 넘
   어가고, 이런 식으로 iterables의 모든 이터러블이 소진될 때까지 진행
   하는 이터레이터를 만듭니다. 여러 시퀀스를 단일 시퀀스처럼 처리하는
   데 사용됩니다. 대략 다음과 동등합니다:

      def chain(*iterables):
          # chain('ABC', 'DEF') --> A B C D E F
          for it in iterables:
              for element in it:
                  yield element

classmethod chain.from_iterable(iterable)

   "chain()"의 대체 생성자. 게으르게 평가되는 단일 이터러블 인자에서
   연쇄 입력을 가져옵니다. 대략 다음과 동등합니다:

      def from_iterable(iterables):
          # chain.from_iterable(['ABC', 'DEF']) --> A B C D E F
          for it in iterables:
              for element in it:
                  yield element

itertools.combinations(iterable, r)

   입력 *iterable*에서 요소의 길이 *r* 서브 시퀀스들을 반환합니다.

   조합(combination) 튜플은 입력 *iterable*의 순서에 따라 사전식 순서
   로 방출됩니다. 따라서, 입력 *iterable*이 정렬되어있으면, 조합 튜플
   이 정렬된 순서로 생성됩니다.

   요소는 값이 아니라 위치로 고유성을 다룹니다. 따라서 입력 요소가 고
   유하면, 각 조합에 반복 값이 없습니다.

   대략 다음과 동등합니다:

      def combinations(iterable, r):
          # combinations('ABCD', 2) --> AB AC AD BC BD CD
          # combinations(range(4), 3) --> 012 013 023 123
          pool = tuple(iterable)
          n = len(pool)
          if r > n:
              return
          indices = list(range(r))
          yield tuple(pool[i] for i in indices)
          while True:
              for i in reversed(range(r)):
                  if indices[i] != i + n - r:
                      break
              else:
                  return
              indices[i] += 1
              for j in range(i+1, r):
                  indices[j] = indices[j-1] + 1
              yield tuple(pool[i] for i in indices)

   "combinations()"의 코드는 요소가 정렬된 순서(입력 풀에서의 위치에
   따라)가 아닌 항목을 걸러내어 만들어지는 "permutations()"의 서브 시
   퀀스로 표현될 수도 있습니다:

      def combinations(iterable, r):
          pool = tuple(iterable)
          n = len(pool)
          for indices in permutations(range(n), r):
              if sorted(indices) == list(indices):
                  yield tuple(pool[i] for i in indices)

   반환되는 항목 수는 "0 <= r <= n"일 때는 "n! / r! / (n-r)!" 이고 "r
   > n"일 때는 0입니다.

itertools.combinations_with_replacement(iterable, r)

   입력 *iterable*에서 요소의 길이 *r* 서브 시퀀스들을 반환하는데, 개
   별 요소를 두 번 이상 반복할 수 있습니다.

   조합(combination) 튜플은 입력 *iterable*의 순서에 따라 사전식 순서
   로 방출됩니다. 따라서, 입력 *iterable*이 정렬되어있으면, 조합 튜플
   이 정렬된 순서로 생성됩니다.

   요소는 값이 아니라 위치로 고유성을 다룹니다. 따라서 입력 요소가 고
   유하면, 생성된 조합도 고유합니다.

   대략 다음과 동등합니다:

      def combinations_with_replacement(iterable, r):
          # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC
          pool = tuple(iterable)
          n = len(pool)
          if not n and r:
              return
          indices = [0] * r
          yield tuple(pool[i] for i in indices)
          while True:
              for i in reversed(range(r)):
                  if indices[i] != n - 1:
                      break
              else:
                  return
              indices[i:] = [indices[i] + 1] * (r - i)
              yield tuple(pool[i] for i in indices)

   "combinations_with_replacement()"의 코드는 요소가 정렬된 순서(입력
   풀에서의 위치에 따라)가 아닌 항목을 걸러내어 만들어지는 "product()"
   의 서브 시퀀스로 표현될 수도 있습니다:

      def combinations_with_replacement(iterable, r):
          pool = tuple(iterable)
          n = len(pool)
          for indices in product(range(n), repeat=r):
              if sorted(indices) == list(indices):
                  yield tuple(pool[i] for i in indices)

   반환되는 항목 수는 "n > 0"일 때 "(n+r-1)! / r! / (n-1)!" 입니다.

   버전 3.1에 추가.

itertools.compress(data, selectors)

   *data*에서 요소를 필터링하여 *selectors*에서 "True"로 평가되는 해당
   요소들만 반환하는 이터레이터를 만듭니다. *data*나 *selectors* 이터
   러블이 모두 소진되면 중지합니다. 대략 다음과 동등합니다:

      def compress(data, selectors):
          # compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F
          return (d for d, s in zip(data, selectors) if s)

   버전 3.1에 추가.

itertools.count(start=0, step=1)

   숫자 *start*로 시작하여 균등 간격의 값을 반환하는 이터레이터를 만듭
   니다. 연속적인 데이터 포인트를 생성하기 위해 "map()"에 대한 인자로
   종종 사용됩니다. 또한, 시퀀스 번호를 추가하기 위해 "zip()"과 함께
   사용됩니다. 대략 다음과 동등합니다:

      def count(start=0, step=1):
          # count(10) --> 10 11 12 13 14 ...
          # count(2.5, 0.5) -> 2.5 3.0 3.5 ...
          n = start
          while True:
              yield n
              n += step

   부동 소수점 숫자로 count 할 때, "(start + step * i for i in
   count())"와 같은 곱셈 코드를 대체하여 때로 더 나은 정확도를 얻을 수
   있습니다.

   버전 3.1에서 변경: *step* 인자를 추가하고 정수가 아닌 인자를 허용했
   습니다.

itertools.cycle(iterable)

   iterable에서 요소를 반환하고 각 사본을 저장하는 이터레이터를 만듭니
   다. iterable이 소진되면, 저장된 사본에서 요소를 반환합니다. 무한히
   반복합니다. 대략 다음과 동등합니다:

      def cycle(iterable):
          # cycle('ABCD') --> A B C D A B C D A B C D ...
          saved = []
          for element in iterable:
              yield element
              saved.append(element)
          while saved:
              for element in saved:
                    yield element

   툴킷의 이 멤버에는 iterable의 길이에 따라 상당한 보조 기억 장치가
   필요할 수 있음에 유의하십시오.

itertools.dropwhile(predicate, iterable)

   술어(predicate)가 참인 한 iterable에서 요소를 걸러내는 이터레이터를
   만듭니다; 그 후에는 모든 요소를 반환합니다. 술어(predicate)가 처음
   거짓이 될 때까지 이터레이터는 *아무런* 출력도 생성하지 않아서 시작
   소요 시간이 길어질 수 있음에 유의하십시오. 대략 다음과 동등합니다:

      def dropwhile(predicate, iterable):
          # dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1
          iterable = iter(iterable)
          for x in iterable:
              if not predicate(x):
                  yield x
                  break
          for x in iterable:
              yield x

itertools.filterfalse(predicate, iterable)

   iterable에서 요소를 걸러내어 술어(predicate)가 "False"인 요소만 반
   환하는 이터레이터를 만듭니다. *predicate*가 "None"이면, 거짓인 항목
   을 반환합니다. 대략 다음과 동등합니다:

      def filterfalse(predicate, iterable):
          # filterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8
          if predicate is None:
              predicate = bool
          for x in iterable:
              if not predicate(x):
                  yield x

itertools.groupby(iterable, key=None)

   *iterable*에서 연속적인 키와 그룹을 반환하는 이터레이터를 만듭니다.
   *key*는 각 요소의 키값을 계산하는 함수입니다. 지정되지 않거나
   "None"이면, *key*의 기본값은 항등함수(identity function)이고 요소를
   변경하지 않고 반환합니다. 일반적으로, iterable은 같은 키 함수로 이
   미 정렬되어 있어야 합니다.

   "groupby()"의 작동은 유닉스의 "uniq" 필터와 유사합니다. 키 함수의
   값이 변경될 때마다 중단(break)이나 새 그룹을 생성합니다 (이것이 일
   반적으로 같은 키 함수를 사용하여 데이터를 정렬해야 하는 이유입니다
   ). 이 동작은 입력 순서와 관계없이 공통 요소를 집계하는 SQL의 GROUP
   BY와 다릅니다.

   반환되는 그룹 자체는 "groupby()"와 하부 이터러블(iterable)을 공유하
   는 이터레이터입니다. 소스가 공유되므로, "groupby()" 객체가 진행하면
   , 이전 그룹은 이 더는 보이지 않게 됩니다. 따라서, 나중에 데이터가
   필요하면, 리스트로 저장해야 합니다:

      groups = []
      uniquekeys = []
      data = sorted(data, key=keyfunc)
      for k, g in groupby(data, keyfunc):
          groups.append(list(g))      # Store group iterator as a list
          uniquekeys.append(k)

   "groupby()"는 대략 다음과 동등합니다:

      class groupby:
          # [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
          # [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
          def __init__(self, iterable, key=None):
              if key is None:
                  key = lambda x: x
              self.keyfunc = key
              self.it = iter(iterable)
              self.tgtkey = self.currkey = self.currvalue = object()
          def __iter__(self):
              return self
          def __next__(self):
              self.id = object()
              while self.currkey == self.tgtkey:
                  self.currvalue = next(self.it)    # Exit on StopIteration
                  self.currkey = self.keyfunc(self.currvalue)
              self.tgtkey = self.currkey
              return (self.currkey, self._grouper(self.tgtkey, self.id))
          def _grouper(self, tgtkey, id):
              while self.id is id and self.currkey == tgtkey:
                  yield self.currvalue
                  try:
                      self.currvalue = next(self.it)
                  except StopIteration:
                      return
                  self.currkey = self.keyfunc(self.currvalue)

itertools.islice(iterable, stop)
itertools.islice(iterable, start, stop[, step])

   iterable에서 선택된 요소를 반환하는 이터레이터를 만듭니다. *start*
   가 0이 아니면, iterable의 요소는 start에 도달할 때까지 건너뜁니다.
   그 후에는 *step*이 1보다 크게 설정(이때는 항목을 건너뛰게 됩니다)되
   지 않는 한 요소가 연속적으로 반환됩니다. *stop*이 "None"이면, 이터
   레이터가 완전히 소진될 때까지 이터레이션이 계속됩니다 (소진한다면);
   그렇지 않으면, 지정된 위치에서 멈춥니다. 일반 슬라이싱과 달리,
   "islice()"는 *start*, *stop* 또는 *step*에 대해 음수 값을 지원하지
   않습니다. 내부 구조가 평탄화된 데이터에서 관련 필드를 추출하는 데
   사용할 수 있습니다 (예를 들어, 여러 줄 보고서가 세 번째 줄마다 이름
   필드를 나열할 수 있습니다). 대략 다음과 동등합니다:

      def islice(iterable, *args):
          # islice('ABCDEFG', 2) --> A B
          # islice('ABCDEFG', 2, 4) --> C D
          # islice('ABCDEFG', 2, None) --> C D E F G
          # islice('ABCDEFG', 0, None, 2) --> A C E G
          s = slice(*args)
          start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1
          it = iter(range(start, stop, step))
          try:
              nexti = next(it)
          except StopIteration:
              # Consume *iterable* up to the *start* position.
              for i, element in zip(range(start), iterable):
                  pass
              return
          try:
              for i, element in enumerate(iterable):
                  if i == nexti:
                      yield element
                      nexti = next(it)
          except StopIteration:
              # Consume to *stop*.
              for i, element in zip(range(i + 1, stop), iterable):
                  pass

   *start*가 "None"이면, 이터레이션은 0에서 시작합니다. *step*이
   "None"이면, step의 기본값은 1입니다.

itertools.permutations(iterable, r=None)

   *iterable*에서 요소의 연속된 길이 *r* 순열을 반환합니다.

   *r*이 지정되지 않았거나 "None"이면, *r*의 기본값은 *iterable*의 길
   이이며 가능한 모든 최대 길이 순열이 생성됩니다.

   순열(permutation) 튜플은 입력 *iterable*의 순서에 따라 사전식 순서
   로 방출됩니다. 따라서, 입력 *iterable*이 정렬되어 있으면, 순열 튜플
   이 정렬된 순서로 생성됩니다.

   요소는 값이 아니라 위치로 고유성을 다룹니다. 따라서 입력 요소가 고
   유하면, 각 순열에 반복 값이 없습니다.

   대략 다음과 동등합니다:

      def permutations(iterable, r=None):
          # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
          # permutations(range(3)) --> 012 021 102 120 201 210
          pool = tuple(iterable)
          n = len(pool)
          r = n if r is None else r
          if r > n:
              return
          indices = list(range(n))
          cycles = list(range(n, n-r, -1))
          yield tuple(pool[i] for i in indices[:r])
          while n:
              for i in reversed(range(r)):
                  cycles[i] -= 1
                  if cycles[i] == 0:
                      indices[i:] = indices[i+1:] + indices[i:i+1]
                      cycles[i] = n - i
                  else:
                      j = cycles[i]
                      indices[i], indices[-j] = indices[-j], indices[i]
                      yield tuple(pool[i] for i in indices[:r])
                      break
              else:
                  return

   "permutations()"의 코드는 반복되는 요소(입력 풀에서 같은 위치에 있
   는 요소)가 있는 항목을 제외하도록 걸러낸 "product()"의 서브 시퀀스
   로 표현될 수도 있습니다:

      def permutations(iterable, r=None):
          pool = tuple(iterable)
          n = len(pool)
          r = n if r is None else r
          for indices in product(range(n), repeat=r):
              if len(set(indices)) == r:
                  yield tuple(pool[i] for i in indices)

   반환되는 항목 수는 "0 <= r <= n"일 때는 "n! / (n-r)!" 이고 "r > n"
   일 때는 0입니다.

itertools.product(*iterables, repeat=1)

   입력 이터러블들(iterables)의 데카르트 곱.

   대략 제너레이터 표현식에서의 중첩된 for-루프와 동등합니다. 예를 들
   어, "product(A, B)"는 "((x,y) for x in A for y in B)"와 같은 것을
   반환합니다.

   중첩된 루프는 매 이터레이션마다 가장 오른쪽 요소가 진행되는 주행 거
   리계처럼 순환합니다. 이 패턴은 사전식 순서를 만들어서 입력의 이터러
   블들이 정렬되어 있다면, 곱(product) 튜플이 정렬된 순서로 방출됩니다
   .

   이터러블의 자신과의 곱을 계산하려면, 선택적 *repeat* 키워드 인자를
   사용하여 반복 횟수를 지정하십시오. 예를 들어, "product(A,
   repeat=4)"는 "product(A, A, A, A)"와 같은 것을 뜻합니다.

   이 함수는 실제 구현이 메모리에 중간 결과를 쌓지 않는다는 점을 제외
   하고 다음 코드와 대략 동등합니다:

      def product(*args, repeat=1):
          # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
          # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
          pools = [tuple(pool) for pool in args] * repeat
          result = [[]]
          for pool in pools:
              result = [x+[y] for x in result for y in pool]
          for prod in result:
              yield tuple(prod)

   "product()"가 실행되기 전에, 입력 이터러블을 완전히 소비하여, 곱을
   생성하기 위해 값의 풀(pool)을 메모리에 유지합니다. 따라서, 유한 입
   력에만 유용합니다.

itertools.repeat(object[, times])

   *object*를 반복해서 반환하는 이터레이터를 만듭니다. *times* 인자가
   지정되지 않으면 무기한 실행됩니다. 호출되는 함수에 대한 불변 매개
   변수를 위해 "map()"에 대한 인자로 사용됩니다. "zip()"과 함께 사용하
   여 튜플 레코드의 불변 부분을 만들기도 합니다.

   대략 다음과 동등합니다:

      def repeat(object, times=None):
          # repeat(10, 3) --> 10 10 10
          if times is None:
              while True:
                  yield object
          else:
              for i in range(times):
                  yield object

   *repeat*의 일반적인 용도는 *map*이나 *zip*에 상숫값 스트림을 제공하
   는 것입니다:

      >>> list(map(pow, range(10), repeat(2)))
      [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

itertools.starmap(function, iterable)

   iterable에서 얻은 인자를 사용하여 함수를 계산하는 이터레이터를 만듭
   니다. 인자 매개 변수가 이미 단일 이터러블에 튜플로 그룹화되어있을
   때 (데이터가 "미리 zip" 되었을 때) "map()" 대신 사용됩니다. "map()"
   과 "starmap()"의 차이는 "function(a,b)"와 "function(*c)"의 차이와
   유사합니다. 대략 다음과 동등합니다:

      def starmap(function, iterable):
          # starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000
          for args in iterable:
              yield function(*args)

itertools.takewhile(predicate, iterable)

   술어(predicate)가 참인 한 iterable에서 요소를 반환하는 이터레이터를
   만듭니다. 대략 다음과 동등합니다:

      def takewhile(predicate, iterable):
          # takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4
          for x in iterable:
              if predicate(x):
                  yield x
              else:
                  break

itertools.tee(iterable, n=2)

   단일 iterable에서 *n* 개의 독립 이터레이터를 반환합니다.

   다음 파이썬 코드는 *tee*의 기능을 설명하는 데 도움이 됩니다 (하지만
   실제 구현은 더 복잡하고 단일 하부 FIFO (선입 선출 - first-in,
   first-out) 큐만 사용합니다).

   대략 다음과 동등합니다:

      def tee(iterable, n=2):
          it = iter(iterable)
          deques = [collections.deque() for i in range(n)]
          def gen(mydeque):
              while True:
                  if not mydeque:             # when the local deque is empty
                      try:
                          newval = next(it)   # fetch a new value and
                      except StopIteration:
                          return
                      for d in deques:        # load it to all the deques
                          d.append(newval)
                  yield mydeque.popleft()
          return tuple(gen(d) for d in deques)

   일단 "tee()"가 분할되면, 원래 *iterable*을 다른 곳에서 사용해서는
   안 됩니다; 그렇지 않으면, tee 객체에 알리지 않고 *iterable*이 진행
   할 수 있습니다.

   "tee" 이터레이터는 스레드 안전하지 않습니다. 원래 *iterable*이 스레
   드 안전해도, 같은 "tee()" 호출로 반환된 이터레이터를 동시에 사용하
   면 "RuntimeError"가 발생할 수 있습니다.

   이 이터레이터 도구에는 상당한 보조 기억 장치가 필요할 수 있습니다 (
   일시적으로 저장해야 하는 데이터양에 따라 다릅니다). 일반적으로, 다
   른 이터레이터가 시작하기 전에 하나의 이터레이터가 대부분이나 모든
   데이터를 사용하면, "tee()" 대신 "list()"를 사용하는 것이 더 빠릅니
   다.

itertools.zip_longest(*iterables, fillvalue=None)

   iterables의 각각에서 요소를 집계하는 이터레이터를 만듭니다. 이터러
   블들의 길이가 고르지 않으면, 누락된 값이 *fillvalue*로 채워집니다.
   가장 긴 이터러블이 소진될 때까지 이터레이션이 계속됩니다. 대략 다음
   과 동등합니다:

      def zip_longest(*args, fillvalue=None):
          # zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
          iterators = [iter(it) for it in args]
          num_active = len(iterators)
          if not num_active:
              return
          while True:
              values = []
              for i, it in enumerate(iterators):
                  try:
                      value = next(it)
                  except StopIteration:
                      num_active -= 1
                      if not num_active:
                          return
                      iterators[i] = repeat(fillvalue)
                      value = fillvalue
                  values.append(value)
              yield tuple(values)

   이터러블 중 하나가 무한할 수 있으면, "zip_longest()" 함수는 호출 수
   를 제한하는 것으로 감싸야 합니다 (예를 들어 "islice()"나
   "takewhile()"). 지정하지 않으면, *fillvalue*의 기본값은 "None"입니
   다.


Itertools 조리법
================

이 섹션에서는 기존 itertools를 빌딩 블록으로 사용하여 확장 도구 집합을
만드는 방법을 보여줍니다.

실질적으로 이 모든 조리법과 더 많은 조리법이 파이썬 패키지 색인(Python
Package Index)에서 찾을 수 있는 more-itertools 프로젝트로 설치할 수 있
습니다:

   pip install more-itertools

확장 도구는 하부 도구 집합과 같은 고성능을 제공합니다. 전체 이터러블을
한 번에 메모리로 가져오지 않고 한 번에 하나씩 요소를 처리하여 뛰어난
메모리 성능을 유지합니다. 도구를 함수형(functional) 스타일로 연결하여
임시 변수를 제거함으로써 코드 크기를 작게 유지합니다. 인터프리터 오버
헤드가 발생하는 for-루프와 *제너레이터*를 사용하는 것보다 "벡터화된"
빌딩 블록을 선호하여 고속을 유지합니다.

   def take(n, iterable):
       "Return first n items of the iterable as a list"
       return list(islice(iterable, n))

   def prepend(value, iterator):
       "Prepend a single value in front of an iterator"
       # prepend(1, [2, 3, 4]) -> 1 2 3 4
       return chain([value], iterator)

   def tabulate(function, start=0):
       "Return function(0), function(1), ..."
       return map(function, count(start))

   def tail(n, iterable):
       "Return an iterator over the last n items"
       # tail(3, 'ABCDEFG') --> E F G
       return iter(collections.deque(iterable, maxlen=n))

   def consume(iterator, n=None):
       "Advance the iterator n-steps ahead. If n is None, consume entirely."
       # Use functions that consume iterators at C speed.
       if n is None:
           # feed the entire iterator into a zero-length deque
           collections.deque(iterator, maxlen=0)
       else:
           # advance to the empty slice starting at position n
           next(islice(iterator, n, n), None)

   def nth(iterable, n, default=None):
       "Returns the nth item or a default value"
       return next(islice(iterable, n, None), default)

   def all_equal(iterable):
       "Returns True if all the elements are equal to each other"
       g = groupby(iterable)
       return next(g, True) and not next(g, False)

   def quantify(iterable, pred=bool):
       "Count how many times the predicate is true"
       return sum(map(pred, iterable))

   def pad_none(iterable):
       """Returns the sequence elements and then returns None indefinitely.

       Useful for emulating the behavior of the built-in map() function.
       """
       return chain(iterable, repeat(None))

   def ncycles(iterable, n):
       "Returns the sequence elements n times"
       return chain.from_iterable(repeat(tuple(iterable), n))

   def dotproduct(vec1, vec2):
       return sum(map(operator.mul, vec1, vec2))

   def convolve(signal, kernel):
       # See:  https://betterexplained.com/articles/intuitive-convolution/
       # convolve(data, [0.25, 0.25, 0.25, 0.25]) --> Moving average (blur)
       # convolve(data, [1, -1]) --> 1st finite difference (1st derivative)
       # convolve(data, [1, -2, 1]) --> 2nd finite difference (2nd derivative)
       kernel = tuple(kernel)[::-1]
       n = len(kernel)
       window = collections.deque([0], maxlen=n) * n
       for x in chain(signal, repeat(0, n-1)):
           window.append(x)
           yield sum(map(operator.mul, kernel, window))

   def flatten(list_of_lists):
       "Flatten one level of nesting"
       return chain.from_iterable(list_of_lists)

   def repeatfunc(func, times=None, *args):
       """Repeat calls to func with specified arguments.

       Example:  repeatfunc(random.random)
       """
       if times is None:
           return starmap(func, repeat(args))
       return starmap(func, repeat(args, times))

   def pairwise(iterable):
       "s -> (s0,s1), (s1,s2), (s2, s3), ..."
       a, b = tee(iterable)
       next(b, None)
       return zip(a, b)

   def grouper(iterable, n, fillvalue=None):
       "Collect data into fixed-length chunks or blocks"
       # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
       args = [iter(iterable)] * n
       return zip_longest(*args, fillvalue=fillvalue)

   def roundrobin(*iterables):
       "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
       # Recipe credited to George Sakkis
       num_active = len(iterables)
       nexts = cycle(iter(it).__next__ for it in iterables)
       while num_active:
           try:
               for next in nexts:
                   yield next()
           except StopIteration:
               # Remove the iterator we just exhausted from the cycle.
               num_active -= 1
               nexts = cycle(islice(nexts, num_active))

   def partition(pred, iterable):
       "Use a predicate to partition entries into false entries and true entries"
       # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
       t1, t2 = tee(iterable)
       return filterfalse(pred, t1), filter(pred, t2)

   def powerset(iterable):
       "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
       s = list(iterable)
       return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

   def unique_everseen(iterable, key=None):
       "List unique elements, preserving order. Remember all elements ever seen."
       # unique_everseen('AAAABBBCCDAABBB') --> A B C D
       # unique_everseen('ABBCcAD', str.lower) --> A B C D
       seen = set()
       seen_add = seen.add
       if key is None:
           for element in filterfalse(seen.__contains__, iterable):
               seen_add(element)
               yield element
       else:
           for element in iterable:
               k = key(element)
               if k not in seen:
                   seen_add(k)
                   yield element

   def unique_justseen(iterable, key=None):
       "List unique elements, preserving order. Remember only the element just seen."
       # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
       # unique_justseen('ABBCcAD', str.lower) --> A B C A D
       return map(next, map(operator.itemgetter(1), groupby(iterable, key)))

   def iter_except(func, exception, first=None):
       """ Call a function repeatedly until an exception is raised.

       Converts a call-until-exception interface to an iterator interface.
       Like builtins.iter(func, sentinel) but uses an exception instead
       of a sentinel to end the loop.

       Examples:
           iter_except(functools.partial(heappop, h), IndexError)   # priority queue iterator
           iter_except(d.popitem, KeyError)                         # non-blocking dict iterator
           iter_except(d.popleft, IndexError)                       # non-blocking deque iterator
           iter_except(q.get_nowait, Queue.Empty)                   # loop over a producer Queue
           iter_except(s.pop, KeyError)                             # non-blocking set iterator

       """
       try:
           if first is not None:
               yield first()            # For database APIs needing an initial cast to db.first()
           while True:
               yield func()
       except exception:
           pass

   def first_true(iterable, default=False, pred=None):
       """Returns the first true value in the iterable.

       If no true value is found, returns *default*

       If *pred* is not None, returns the first item
       for which pred(item) is true.

       """
       # first_true([a,b,c], x) --> a or b or c or x
       # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
       return next(filter(pred, iterable), default)

   def random_product(*args, repeat=1):
       "Random selection from itertools.product(*args, **kwds)"
       pools = [tuple(pool) for pool in args] * repeat
       return tuple(map(random.choice, pools))

   def random_permutation(iterable, r=None):
       "Random selection from itertools.permutations(iterable, r)"
       pool = tuple(iterable)
       r = len(pool) if r is None else r
       return tuple(random.sample(pool, r))

   def random_combination(iterable, r):
       "Random selection from itertools.combinations(iterable, r)"
       pool = tuple(iterable)
       n = len(pool)
       indices = sorted(random.sample(range(n), r))
       return tuple(pool[i] for i in indices)

   def random_combination_with_replacement(iterable, r):
       "Random selection from itertools.combinations_with_replacement(iterable, r)"
       pool = tuple(iterable)
       n = len(pool)
       indices = sorted(random.choices(range(n), k=r))
       return tuple(pool[i] for i in indices)

   def nth_combination(iterable, r, index):
       "Equivalent to list(combinations(iterable, r))[index]"
       pool = tuple(iterable)
       n = len(pool)
       if r < 0 or r > n:
           raise ValueError
       c = 1
       k = min(r, n-r)
       for i in range(1, k+1):
           c = c * (n - k + i) // i
       if index < 0:
           index += c
       if index < 0 or index >= c:
           raise IndexError
       result = []
       while r:
           c, n, r = c*r//n, n-1, r-1
           while index >= c:
               index -= c
               c, n = c*(n-r)//n, n-1
           result.append(pool[-1-n])
       return tuple(result)
