"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.

   Esta função primeiro executa "bisect_left()" para localizar um
   ponto de inserção. Em seguida, ele executa o método "insert()" em
   *a* para inserir *x* na posição apropriada para manter a ordem de
   classificação.

   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*.

   Esta função primeiro executa "bisect_right()" para localizar um
   ponto de inserção. Em seguida, ele executa o método "insert()" em
   *a* para inserir *x* na posição apropriada para manter a ordem de
   classificação.

   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.

* As funções de busca são stateless e descartam os resultados da
  função chave depois que são usadas. Consequentemente, se as funções
  de busca forem usadas em um laço, a função chave pode ser chamada
  repetidamente nos mesmos elementos do vetor. Se a função chave não
  for rápida, considere envolvê-la com "functools.cache()" para evitar
  cálculos duplicados. Como alternativa, considere buscar um vetor de
  chaves pré-calculadas para localizar o ponto de inserção (conforme
  mostrado na seção de exemplos abaixo).

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 de bisseção acima são úteis para encontrar pontos de
inserção, mas podem ser complicadas ou difíceis de usar para tarefas
comuns de busca. As cinco funções a seguir mostram como transformá-las
nas buscas 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 buscas em tabelas numéricas.
Este exemplo usa "bisect()" para buscar 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']

As funções "bisect()" e "insort()" também funcionam com listas de
tuplas. O argumento *key* pode servir para extrair o campo usado para
ordenar registros em uma tabela:

   >>> 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, 'Spielberg'),
   ...     Movie('Titanic', 1997, 'Cameron'),
   ...     Movie('The Birds', 1963, 'Hitchcock'),
   ...     Movie('Aliens', 1986, 'Cameron')
   ... ]

   >>> # 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='Spielberg'),
    Movie(name='Aliens', released=1986, director='Cameron'),
    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)
