"bisect" --- Algoritmo de bisseção de vetor
*******************************************

**Código-fonte:** Lib/bisect.py

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

Este módulo fornece suporte para manter uma lista em ordem de
classificação sem ter que classificar a lista após cada inserção. Para
longas listas de itens com operações de comparação custosas, isso pode
ser uma melhoria em relação à abordagem mais comum. O módulo é
denominado "bisect" porque usa um algoritmo de bisseção básico para
fazer seu trabalho. O código-fonte pode ser mais útil como um exemplo
funcional de algoritmo (as condições fronteiriças  já estão certas!).

As seguintes funções são fornecidas:

bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None)

   Localiza o ponto de inserção de *x* em *a* para manter a ordem de
   classificação. Os parâmetros *lo* e *hi* podem ser usados para
   especificar um subconjunto da lista que deve ser considerado; por
   padrão, toda a lista é usada. Se *x* já estiver presente em *a*, o
   ponto de inserção estará antes (à esquerda) de qualquer entrada
   existente. O valor de retorno é adequado para uso como o primeiro
   parâmetro para "list.insert()" supondo que *a* já esteja ordenado.

   The returned insertion point *i* partitions the array *a* into two
   halves so that "all(val < x for val in a[lo : i])" for the left
   side and "all(val >= x for val in a[i : hi])" for the right side.

   *key* especifica uma *função chave* de um argumento que é usado
   para extrair uma chave de comparação de cada elemento no vetor.
   Para oferecer suporte à pesquisa de registros complexos, a função
   chave não é aplicada ao valor *x*.

   If *key* is "None", the elements are compared directly with no
   intervening function call.

   Alterado na versão 3.10: Adicionado o  parâmetro *key*.

bisect.bisect_right(a, x, lo=0, hi=len(a), *, key=None)
bisect.bisect(a, x, lo=0, hi=len(a), *, key=None)

   Semelhante a "bisect_left()", mas retorna um ponto de inserção que
   vem depois (à direita de) qualquer entrada existente de *x* em *a*.

   The returned insertion point *i* partitions the array *a* into two
   halves so that "all(val <= x for val in a[lo : i])" for the left
   side and "all(val > x for val in a[i : hi])" for the right side.

   *key* especifica uma *função chave* de um argumento que é usado
   para extrair uma chave de comparação de cada elemento no vetor.
   Para oferecer suporte à pesquisa de registros complexos, a função
   chave não é aplicada ao valor *x*.

   If *key* is "None", the elements are compared directly with no
   intervening function call.

   Alterado na versão 3.10: Adicionado o  parâmetro *key*.

bisect.insort_left(a, x, lo=0, hi=len(a), *, key=None)

   Insere *x* em *a* na ordem de classificação.

   This function first runs "bisect_left()" to locate an insertion
   point. Next, it runs the "insert()" method on *a* to insert *x* at
   the appropriate position to maintain sort order.

   Para oferecer suporte à inserção de registros em uma tabela, a
   função *key* (se houver) é aplicada a *x* para a etapa de pesquisa,
   mas não para a etapa de inserção.

   Tenha em mente que a busca "O(log n)" é dominada pelo etapa de
   inserção lenta O(n).

   Alterado na versão 3.10: Adicionado o  parâmetro *key*.

bisect.insort_right(a, x, lo=0, hi=len(a), *, key=None)
bisect.insort(a, x, lo=0, hi=len(a), *, key=None)

   Semelhante a "insort_left()", mas inserindo *x* em *a* após
   qualquer entrada existente de *x*.

   This function first runs "bisect_right()" to locate an insertion
   point. Next, it runs the "insert()" method on *a* to insert *x* at
   the appropriate position to maintain sort order.

   Para oferecer suporte à inserção de registros em uma tabela, a
   função *key* (se houver) é aplicada a *x* para a etapa de pesquisa,
   mas não para a etapa de inserção.

   Tenha em mente que a busca "O(log n)" é dominada pelo etapa de
   inserção lenta O(n).

   Alterado na versão 3.10: Adicionado o  parâmetro *key*.


Observações sobre desempenho
============================

Ao escrever um código sensível ao tempo usando *bisect()* e
*insort()*, lembre-se do seguinte:

* A bisseção é eficaz para pesquisar intervalos de valores. Para
  localizar valores específicos, os dicionários são mais eficientes.

* As funções *insort()* são "O(n)" porque a etapa de busca logarítmica
  é dominada pela etapa de inserção de tempo linear.

* The search functions are stateless and discard key function results
  after they are used.  Consequently, if the search functions are used
  in a loop, the key function may be called again and again on the
  same array elements. If the key function isn't fast, consider
  wrapping it with "functools.cache()" to avoid duplicate
  computations.  Alternatively, consider searching an array of
  precomputed keys to locate the insertion point (as shown in the
  examples section below).

Ver também:

  * Sorted Collections é um módulo de alto desempenho que usa bisseção
    para gerenciar coleções de dados classificadas.

  * A receita de SortedCollection usa bisseção para construir uma
    classe de coleção completa com métodos de busca diretos e suporte
    para uma função chave. As chaves são pré-calculadas para
    economizar em chamadas desnecessárias para a função chave durante
    as buscas.


Buscando em listas ordenadas
============================

As funções "bisect()" acima são úteis para encontrar pontos de
inserção, mas podem ser complicadas ou difíceis de usar para tarefas
comuns de pesquisa. As cinco funções a seguir mostram como
transformá-las nas pesquisas padrão para listas ordenadas:

   def index(a, x):
       'Locate the leftmost value exactly equal to x'
       i = bisect_left(a, x)
       if i != len(a) and a[i] == x:
           return i
       raise ValueError

   def find_lt(a, x):
       'Find rightmost value less than x'
       i = bisect_left(a, x)
       if i:
           return a[i-1]
       raise ValueError

   def find_le(a, x):
       'Find rightmost value less than or equal to x'
       i = bisect_right(a, x)
       if i:
           return a[i-1]
       raise ValueError

   def find_gt(a, x):
       'Find leftmost value greater than x'
       i = bisect_right(a, x)
       if i != len(a):
           return a[i]
       raise ValueError

   def find_ge(a, x):
       'Find leftmost item greater than or equal to x'
       i = bisect_left(a, x)
       if i != len(a):
           return a[i]
       raise ValueError


Exemplos
========

A função "bisect()" pode ser útil para pesquisas em tabelas numéricas.
Este exemplo usa "bisect()" para pesquisar uma nota em letra para uma
pontuação de exame (digamos) com base em um conjunto de pontos de
interrupção numéricos ordenados: 90 e acima é um "A", 80 a 89 é um "B"
e por aí vai:

   >>> def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
   ...     i = bisect(breakpoints, score)
   ...     return grades[i]
   ...
   >>> [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
   ['F', 'A', 'C', 'C', 'B', 'A', 'A']

The "bisect()" and "insort()" functions also work with lists of
tuples.  The *key* argument can serve to extract the field used for
ordering records in a table:

   >>> from collections import namedtuple
   >>> from operator import attrgetter
   >>> from bisect import bisect, insort
   >>> from pprint import pprint

   >>> Movie = namedtuple('Movie', ('name', 'released', 'director'))

   >>> movies = [
   ...     Movie('Jaws', 1975, 'Speilberg'),
   ...     Movie('Titanic', 1997, 'Cameron'),
   ...     Movie('The Birds', 1963, 'Hitchcock'),
   ...     Movie('Aliens', 1986, 'Scott')
   ... ]

   >>> # Find the first movie released after 1960
   >>> by_year = attrgetter('released')
   >>> movies.sort(key=by_year)
   >>> movies[bisect(movies, 1960, key=by_year)]
   Movie(name='The Birds', released=1963, director='Hitchcock')

   >>> # Insert a movie while maintaining sort order
   >>> romance = Movie('Love Story', 1970, 'Hiller')
   >>> insort(movies, romance, key=by_year)
   >>> pprint(movies)
   [Movie(name='The Birds', released=1963, director='Hitchcock'),
    Movie(name='Love Story', released=1970, director='Hiller'),
    Movie(name='Jaws', released=1975, director='Speilberg'),
    Movie(name='Aliens', released=1986, director='Scott'),
    Movie(name='Titanic', released=1997, director='Cameron')]

Se a função chave for custosa, é possível evitar chamadas de função
repetidas buscando uma lista de chaves pré-calculadas para encontrar o
índice de um registro:

   >>> data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
   >>> data.sort(key=lambda r: r[1])       # Or use operator.itemgetter(1).
   >>> keys = [r[1] for r in data]         # Precompute a list of keys.
   >>> data[bisect_left(keys, 0)]
   ('black', 0)
   >>> data[bisect_left(keys, 1)]
   ('blue', 1)
   >>> data[bisect_left(keys, 5)]
   ('red', 5)
   >>> data[bisect_left(keys, 8)]
   ('yellow', 8)
