Este artigo é a parte 6 de 10 na série Python3

Conjuntos são um tipo especial de dados do Python com características bem específicas, principalmente para serem empregados como chaves em dicionários e bancos de dados.

1. Conjuntos (set)

Conjunto é uma estrutura de dados mutável, composto de uma coletânea de objetos hashable desordenados e com elementos únicos, ou seja, sem duplicatas. Conjuntos ainda suportam operações matemáticas de união, interseção, diferença e diferença simétrica.

Objetos hashable são todos aqueles que possuem o método __hash__(), o qual retorna um valor inteiro constante, ou seja, o mesmo valor por toda a vida do objeto. Objetos com o mesmo hash() são considerados idênticos quando empregados como índice. Isto ficará mais claro quando for tratado sobre a estrutura de dados dicionário. Entretanto, esta característica é indispensável para que conjuntos possam serem empregados como chaves em dicionários, como será mostrado no próximo texto1.

Um conjunto pode ser criado usando chaves ou pela função set(), passando como elementos qualquer conjunto de objetos ou mesmo um iterável. Veja os comandos a seguir:

[xterm color=’true’ py=’true’]
>>> cesta = {‘laranja’, ‘goiaba’, ‘banana’, ‘maça’, ‘abacate’, ‘banana’, ‘goiaba’}
>>> cesta
{‘banana’, ‘abacate’, ‘goiaba’, ‘laranja’, ‘maça’}
>>> a = set(‘São Paulo’)
>>> a
{‘o’, ‘l’, ‘P’, ‘a’, ‘u’, ‘ã’, ‘S’, ‘ ‘}
>>> b = set(‘Espírito Santo’)
>>> b
{‘p’, ‘i’, ‘í’, ‘t’, ‘r’, ‘s’, ‘o’, ‘E’, ‘a’, ‘n’, ‘S’, ‘ ‘}
>>> c = {1, 2.14, ‘três’, 5j, (1,0,0)}
>>> c
{1, 2.14, (1, 0, 0), 5j, ‘três’}
[/xterm]

No primeiro conjunto, cesta, são adicionadas diversas frutas, mas as repetidas são excluídas do conjunto. Em seguida, são criados os dois conjuntos a e b, com as letas das strings ‘São Paulo’ e ‘Espírito Santo’. Observe que a ordem dos elementos dos conjuntos não é a ordem de entrada, ou seja, não são ordenados. Por fim, o conjunto c utiliza uma diversidade de tipos de objetos imutáveis, como inteiros, complexo, string, um float e uma tupla. Todos estes imutáveis são hashables e, portanto, retornam um código hash único para elementos diferentes.

[xterm color=’true’ py=’true’]
>>> for i in c:
… out = ‘hash(‘ + str(i) + ‘)’
… print(‘{0:>15} = {1}’.format(out, hash(i)))

hash(1) = 1
hash(2.14) = 322818021289917442
hash((1, 0, 0)) = 2528505496374819208
hash(5j) = 5000015
hash(três) = -5298805650081748838
[/xterm]

Embora conjuntos possam ser empregados em laços de repetição, ou seja, sejam iteráveis, eles também não possuem o método __getitem__() por não serem ordenados, como em listas e tuplas, e por isto não podem ser indexados. Uma tentativa de indexar um elemento de um conjunto gera um erro TypeError:

[xterm color=’true’ py=’true’]
>>> c[1]
Traceback (most recent call last):
File ““, line 1, in
TypeError: ‘set’ object does not support indexing
[/xterm]

1.1. Conjuntos são Mutáveis, frozenset não

Conjuntos são objetos mutáveis e podem ter elementos adicionados e removidos através dos métodos add(), pop() e remove().

[xterm color=’true’ py=’true’]
>>> c.add(5)
>>> c.pop()
1
>>> c.pop()
2.14
>>> c.remove(‘três’)
>>> c
{5, (1, 0, 0), 5j}
[/xterm]

Explico melhor estes métodos mais adiante. Existe um tipo de conjunto que é imutável e também pode ser usado como elemento em um conjunto.

[xterm color=’true’ py=’true’]
>>> f = frozenset({2.8, 9j, ‘elf’})
>>> f
frozenset({‘elf’, 2.8, 9j})
>>> hash(f)
-8561833039905925542
>>> c.add(f)
>>> c
{5, (1, 0, 0), 5j, frozenset({‘elf’, 2.8, 9j})}
[/xterm]

FrozenSet são conjuntos imutáveis.

1.2. Operadores em Conjuntos

Conjuntos suportam um grupo específico de operadores específicos além dos operadores convencionais de comparação e operação, mas com comportamentos um pouco distintos.

Para os exemplos a seguir, considere os conteúdos dos conjuntos a e b como sendo o conjunto de letras nas strings ‘laranja’ e ‘marajo’, respectivamente:

[xterm color=’true’ py=’true’]
>>> a = set(‘laranja’)
>>> b = set(‘marajo’)
>>> a
{‘l’, ‘j’, ‘r’, ‘n’, ‘a’}
>>> b
{‘j’, ‘r’, ‘o’, ‘m’, ‘a’}
[/xterm]

Segue a lista de operadores:

  • __and__ – mesmo que o operador &. Retorna a interseção dos conjuntos, ou seja, os elementos comuns entre os dois conjuntos,

    [xterm color=’true’ py=’true’]
    >>> a.__and__(b)
    {‘a’, ‘j’, ‘r’}
    >>> a & b
    {‘a’, ‘j’, ‘r’}
    [/xterm]

  • __or__ – mesmo que o operador |. Retorna a união dos elementos contidos nos dois conjuntos,

    [xterm color=’true’ py=’true’]
    >>> a.__or__(b)
    {‘l’, ‘j’, ‘r’, ‘o’, ‘n’, ‘m’, ‘a’}
    >>> a | b
    {‘l’, ‘j’, ‘r’, ‘o’, ‘n’, ‘m’, ‘a’}
    [/xterm]

  • __xor__ – o mesmo que o operador ^. Retorna a diferença simétrica entre os dois conjuntos, ou seja, os elementos do conjunto a que não estão contidos no conjunto b e os elementos de b que não estão contidos em a,

    [xterm color=’true’ py=’true’]
    >>> a ^ b
    {‘l’, ‘o’, ‘n’, ‘m’}
    >>> a.__xor__(b)
    {‘l’, ‘o’, ‘n’, ‘m’}
    [/xterm]

  • __sub__ – operador subtração, -. A operação a - b retorna os elementos de a, removidos os elementos semelhantes em b, ou seja apenas os elementos ‘n’ e ‘l’. O contrário, b - a retornará os elementos ‘o’ e ‘m’. Veja no exemplo abaixo:

    [xterm color=’true’ py=’true’]
    >>> a – b
    {‘n’, ‘l’}
    >>> b – a
    {‘o’, ‘m’}
    [/xterm]

    Observe que a operação (a - b) | (b - a) é a definição de a ^ b:

    [xterm color=’true’ py=’true’]
    >>> (a – b) | (b – a) == (a ^ b)
    True
    [/xterm]

  • __iand__, __isub__, __ixor__, __ior__ – são implementações para os operadores &=, -=, ^= e |=, respectivamente.
  • [TABLE=62]

  • __rand__, __rsub__, __rxor__, __ror__ – este ‘r’ corresponde a ‘right’. Essencialmente, implica em trocar os conjuntos de posição nas operações. Por exemplo a.__rxor__(b) = b ^ a, ao invés de a ^ b. Com exceção ao operador de subtração, todos os demais retornaram o mesmo resultado.

    [xterm color=’true’ py=’true’]
    >>> a.__rsub__(b)
    {‘o’, ‘m’}
    >>> a.__rand__(b)
    {‘a’, ‘j’, ‘r’}
    >>> a & b
    {‘a’, ‘j’, ‘r’}
    >>> a & b == b & a
    True
    >>> a – b == b – a
    False
    [/xterm]

  • __eq__, __ne__, __ge__, __gt__, __le__, __lt__ – Operadores condicionais estão definidos para conjuntos, mas operam de forma diferenciada ao visto em lista e tuplas. Como conjuntos são desordenados, os operadores de igualdade e diferença comparam os conjuntos elemento a elemento, independente da ordem que os seus elementos possam ser apresentados.

    [xterm color=’true’ py=’true’]
    >>> a = set(‘abacaxi’)
    >>> b = set(‘xibac’)
    >>> a == b
    True
    >>> a.add(‘f’)
    >>> a == b
    False
    >>> a != b
    True
    [/xterm]

    Os operadores de ‘>‘ e ‘<‘ correspondem aos operadores ‘contém’ e ‘está contido’ em conjuntos.

    [xterm color=’true’ py=’true’]
    >>> a
    {‘b’, ‘c’, ‘a’, ‘i’, ‘f’, ‘x’}
    >>> b
    {‘b’, ‘c’, ‘a’, ‘i’, ‘x’}
    >>> a > b
    True
    >>> b < a
    True
    >>> a < b
    False
    >>> b > a
    False
    [/xterm]

    Como b esta contido em a, as duas primeiras comparações (a > b e b < a) retornam verdadeiras, já as duas seguintes retornam falso.

Como feito até o momento, métodos específicos a construção e atributos da classe set (conjuntos) não foram abordados, deixando este debate para adiante.

1.3. Métodos de Conjuntos

Segue os métodos dos conjuntos.

  • add(Valor) – adiciona o elemento Valor ao conjunto.

    [xterm color=’true’ py=’true’]
    >>> a.add(5)
    >>> a
    {5, ‘l’, ‘j’, ‘r’, ‘a’, ‘n’}
    [/xterm]

  • clear() – apaga todo o conteúdo do conjunto.
  • copy() – cria uma cópia do conteúdo do conjunto. Como conjuntos são mutáveis, um método para fazer a sua cópia é essencial para evitar problemas como o ilustrado com as listas, no exemplo de matriz no texto anterior. Veja o exemplo:

    [xterm color=’true’ py=’true’]
    >>> c = a.copy()
    >>> d = c
    >>> c
    {‘a’, 5, ‘j’, ‘l’, ‘n’, ‘r’}
    >>> a
    {5, ‘l’, ‘j’, ‘r’, ‘a’, ‘n’}
    >>> d
    {‘a’, 5, ‘j’, ‘l’, ‘n’, ‘r’}
    >>> c.clear()
    >>> d
    set()
    >>> a
    {5, ‘l’, ‘j’, ‘r’, ‘a’, ‘n’}
    [/xterm]

    Na primeira linha, c recebe uma cópia de a. Já d apenas aponta para o objeto de c. Ao apagar c, d também é apagado, mas o mesmo não ocorre a a, pois são objetos distintos na memória.

  • difference(set), intersection(set), union(set) e symmetric_difference(set) – estes métodos retornam o mesmo que os operadores -, &, | e ^, respectivamente.

    [xterm color=’true’ py=’true’]
    >>> a.difference(b)
    {‘l’, ‘n’, 5}
    >>> a.intersection(b)
    {‘j’, ‘a’, ‘r’}
    >>> a.union(b)
    {5, ‘l’, ‘j’, ‘r’, ‘m’, ‘o’, ‘a’, ‘n’}
    >>> a.symmetric_difference(b)
    {‘m’, ‘o’, 5, ‘l’, ‘n’}
    [/xterm]

  • difference_update(set), intersection_update(set), update(set) e symmetric_difference_update(set) – estes métodos retornam o mesmo que os operadores compostos -=, &=, |= e ^=, respectivamente. Observe que o método union_update(set) não existe, é apenas update(set).
  • [xterm color=’true’ py=’true’]
    >>> c = a.copy()
    >>> c.difference_update(b)
    >>> c == a – b
    True
    >>> c = a.copy()
    >>> c.intersection_update(b)
    >>> c == a & b
    True
    >>> c = a.copy()
    >>> c.update(b)
    >>> c == a | b
    True
    >>> c = a.copy()
    >>> c.symmetric_difference_update(b)
    >>> c == a ^ b
    True
    [/xterm]

  • pop() – remove um elemento arbitrário do conjunto.

    [xterm color=’true’ py=’true’]
    >>> c
    {‘m’, ‘o’, 5, ‘l’, ‘n’}
    >>> c.pop()
    ‘m’
    >>> c.pop()
    ‘o’
    [/xterm]

  • remove(Valor) – remove o elemento Valor do conjunto. Se o elemento não existir, retorna o erro KeyError.

    [xterm color=’true’ py=’true’]
    >>> c
    {5, ‘l’, ‘n’}
    >>> c.remove(5)
    >>> c.remove(‘n’)
    >>> c.remove(3)
    Traceback (most recent call last):
    File ““, line 1, in
    KeyError: 3
    [/xterm]

  • discard(Valor) – remove o elemento Valor do conjunto. A única diferença com o método remove(Valor) é que o discard(Valor) não retorna erro caso o elemento não exista.

    [xterm color=’true’ py=’true’]
    >>> c = a | b
    >>> c
    {5, ‘l’, ‘j’, ‘r’, ‘m’, ‘o’, ‘a’, ‘n’}
    >>> c.discard(‘a’)
    >>> c
    {5, ‘l’, ‘j’, ‘r’, ‘m’, ‘o’, ‘n’}
    >>> c.discard(3)
    >>>
    [/xterm]

  • isdisjoint(set) – retorna verdadeiro se os dois conjuntos possuem interseção nula.

    [xterm color=’true’ py=’true’]
    >>> c
    {5, ‘l’, ‘j’, ‘r’, ‘m’, ‘o’, ‘n’}
    >>> c -= b
    >>> c
    {5, ‘l’, ‘n’}
    >>> b
    {‘j’, ‘r’, ‘m’, ‘o’, ‘a’}
    >>> c.isdisjoint(b)
    True
    >>> a.isdisjoint(b)
    False
    [/xterm]

  • issubset(set) – retorna verdadeiro se o conjunto for um subconjunto do outro.

    [xterm color=’true’ py=’true’]
    >>> c
    {5, ‘l’, ‘n’}
    >>> a
    {5, ‘l’, ‘j’, ‘r’, ‘a’, ‘n’}
    >>> c.issubset(a)
    True
    >>> a.issubset(c)
    False
    [/xterm]

  • issuperset(set) – retorna verdadeiro se o conjunto argumento (set) for um subconjunto do conjunto. É o contrário do issubset(set).

    [xterm color=’true’ py=’true’]
    >>> c.issuperset(a)
    False
    >>> a.issuperset(c)
    True
    [/xterm]

1.3. Set Comprehensions

Como em listas, conjuntos também podem ser gerados usando um set comprehensions, o qual funciona com uma expressão em um laço de repetição fechado entre chaves. As sintaxes suportadas são as mesmas de listas, apenas substituindo os colchetes por chaves:

{  for  in  }
{  for  in  if  }

Seguem alguns exemplos:

[xterm color=’true’ py=’true’]
>>> a = {x for x in ‘abracadabra’ if x not in ‘abc’}
>>> b = {x for x in ‘abracadabra’ if x > ‘c’}
>>> a
{‘r’, ‘d’}
>>> a == b
True
[/xterm]

Duas formas diferentes de construir o mesmo conjunto das letras de ‘abracadabra’ maiores que c.

2. Considerações

A grande aplicação para conjuntos é o seu emprego como chaves em outras estruturas de dados como dicionários, apresentado próximo texto, ou chaves de bancos de dados, além de aplicações matemáticas como conjuntos. Embora não o tenha utilizado muito, é aconselhável ter consciência de suas possibilidades em momento de programação.

O próximo texto será sobre dicionários, uma estrutura de dados composta, mas que pode empregar qualquer tipo de imutável como índice, além de inteiros. A lista de índices de um dicionário, como veremos, é um conjunto.

  1. Existem alguns detalhes sobre objetos hashables que não consegui compreender ainda. Se tiver uma oportunidade mais adiante, pretendo olhar com mais atenção estes códigos hash.