import numpy as np
    
a = np.arange(6) 
b = a
print("a =\n",a)
print("b =\n",b)
b.shape = (2,3)                           # mudança no shape de b,
print("\na shape =",a.shape)               # altera o shape de a

b[0,0] = -1                               # mudança no conteúdo de b
print("a =\n",a)                             # altera o conteudo de a  

print("\nid de a = ",id(a))                # id é um identificador único de objeto
print("id de b = ",id(b))                  # a e b possuem o mesmo id

print('np.may_share_memory(a,b):',np.may_share_memory(a,b))

"""
Explanation: Table of Contents
<p><div class="lev1 toc-item"><a href="#Introdução-ao-Numpy" data-toc-modified-id="Introdução-ao-Numpy-1"><span class="toc-item-num">1&nbsp;&nbsp;</span>Introdução ao Numpy</a></div><div class="lev2 toc-item"><a href="#Copiando-variáveis-ndarray" data-toc-modified-id="Copiando-variáveis-ndarray-11"><span class="toc-item-num">1.1&nbsp;&nbsp;</span>Copiando variáveis <code>ndarray</code></a></div><div class="lev2 toc-item"><a href="#Sem-cópia-explícita,-apenas-referência" data-toc-modified-id="Sem-cópia-explícita,-apenas-referência-12"><span class="toc-item-num">1.2&nbsp;&nbsp;</span>Sem cópia explícita, apenas referência</a></div><div class="lev2 toc-item"><a href="#Cópia-rasa" data-toc-modified-id="Cópia-rasa-13"><span class="toc-item-num">1.3&nbsp;&nbsp;</span>Cópia rasa</a></div><div class="lev2 toc-item"><a href="#Reshape" data-toc-modified-id="Reshape-14"><span class="toc-item-num">1.4&nbsp;&nbsp;</span>Reshape</a></div><div class="lev2 toc-item"><a href="#Slice---Fatiamento" data-toc-modified-id="Slice---Fatiamento-15"><span class="toc-item-num">1.5&nbsp;&nbsp;</span>Slice - Fatiamento</a></div><div class="lev2 toc-item"><a href="#Transposto" data-toc-modified-id="Transposto-16"><span class="toc-item-num">1.6&nbsp;&nbsp;</span>Transposto</a></div><div class="lev2 toc-item"><a href="#Ravel" data-toc-modified-id="Ravel-17"><span class="toc-item-num">1.7&nbsp;&nbsp;</span>Ravel</a></div><div class="lev2 toc-item"><a href="#Cópia-profunda" data-toc-modified-id="Cópia-profunda-18"><span class="toc-item-num">1.8&nbsp;&nbsp;</span>Cópia profunda</a></div><div class="lev2 toc-item"><a href="#Documentação-Oficial-Numpy" data-toc-modified-id="Documentação-Oficial-Numpy-19"><span class="toc-item-num">1.9&nbsp;&nbsp;</span>Documentação Oficial Numpy</a></div>

# Introdução ao Numpy

## Copiando variáveis ``ndarray``

O ``ndarray`` foi projetado para acesso otimizado a uma grande quantidade de dados. Neste sentido, os conceitos
descritos a seguir sobre as três formas de cópias entre variáveis ditas sem cópia, cópia rasa (*shallow*) e
cópia profunda (*deep*) são fundamentais para uma codificação eficiente. Podemos dizer que um ``ndarray`` possui
o cabeçalho que contém dados pelas informações sobre o tipo do elemento, a dimensionalidade (``shape``) e 
passo ou deslocamento para o próximo elemento (``strides``) e os dados raster em si. A tabela
a seguir mostra a situação do cabeçalho e dos dados nos três tipos de cópias.


Tipo                   |Cabeçalho: Type, Shape, Strides|   Dados raster   |Exemplo
-----------------------|-------------------------------|------------------|---------------------------
Sem cópia, apenas ref  |apontador original             |apontador original|a = b
Cópia rasa             |novo                           |apontador original|b = a.reshape, slicing, a.T
Cópia profunda         |novo                           |novo              |a = b.copy()


## Sem cópia explícita, apenas referência

No caso abaixo, usaremos o comando normal de igual como atribuição do array ``a`` para o array ``b``.
Verifica-se que tanto o shape como os dados de ``b`` são os mesmos de ``a``. Tudo se passa como ``b``
fosse apenas um apontador para ``a``. Qualquer modificação em ``b`` é refletida em ``a``.
End of explanation
"""


def cc(a):
  return a

b = cc(a)
print("id de a = ",id(a))
print("id de b = ",id(b))
print('np.may_share_memory(a,b):',np.may_share_memory(a,b))

"""
Explanation: Observe que mesmo no retorno de uma função, a cópia explícita pode não acontecer. Veja o exemplo a
seguir de uma função que apenas retorna a variável de entrada:
End of explanation
"""


a = np.arange(30)
print("a =\n", a)
print('a.shape:',a.shape)

b = a.reshape( (5, 6))
print("b =\n", b)

b[:, 0] = -1
print('b=\n',b)
print("a =\n", a)

c = a.reshape( (2, 3, 5) )
print("c =\n", c)
print('c.base is a:',c.base is a)
print('np.may_share_memory(a,c):',np.may_share_memory(a,c))
print('id(a),id(c):',id(a),id(c))

"""
Explanation: Cópia rasa
A cópia rasa é muito útil e extensivamente utilizada. É usada quando se quer indexar o array original 
através da mudança de dimensionalidade ou do 
refatiamento, porém sem a necessidade de realizar uma cópia dos dados raster. Desta forma consegue-se
uma otimização no acesso ao array n-dimensional. Existem várias formas onde a cópia rasa acontece,
sendo as principais:
1) no caso do reshape onde o número de elementos do ndarray é o mesmo, porém sua dimensionalidade
é alterada; 
2) no caso de fatiamento onde um subarray é indexado; 
3) no caso de transposição do array;
4) no caso de linearização do raster através do ravel().
entre outros.
Reshape
O exemplo a seguir mostra inicialmente a criação de um vetor unidimensional sequencial sendo "visto" de
forma bidimensional ou tridimensional.
End of explanation
"""


a = np.zeros( (5, 6))
print('a.shape:',a.shape)
b = a[::2,::2]
print('b.shape:',b.shape)
b[:,:] = 1.
print('b=\n', b) 
print('a=\n', a) 
print('b.base is a:',b.base is a)
print('np.may_share_memory(a,b):',np.may_share_memory(a,b))

"""
Explanation: Slice - Fatiamento
O exemplo a seguir mostra a cópia rasa no uso de fatiamento. No exemplo, todos os elementos de linhas
e colunas pares são modificados para 1. CUIDADO: quando é feita a atribuição de b = 1., é importante
que b seja referenciado como ndarray na forma b[:,:], caso contrário, se fizermos b = 1., uma nova 
variável é criada.
End of explanation
"""


a = np.arange(25).reshape((5,5))
print('a=\n',a)
b = a[:,0]
print('b=',b)

b[:] = np.arange(5)
b[2] = 100
print('b=',b)
print('a=\n',a)

a = np.arange(25).reshape((5,5))
print('a=\n',a)
b = np.arange(5)
print('b=',b)
print('a=\n',a)

"""
Explanation: Este outro exemplo é uma forma atraente de processar uma coluna de uma matriz bidimensional,
porém é preciso CUIDADO, pois o uso de b deve ser com b[:] se for atribuído um novo valor para
ele, caso contrário, se fizermos b = arange(5), uma nova variável é criada.
End of explanation
"""


a = np.arange(24).reshape((4,6))
print('a:\n',a)
at = a.T
print('at:\n',at)
print('at.shape',at.shape)
print('np.may_share_memory(a,at):',np.may_share_memory(a,at))

"""
Explanation: Transposto
A operação matricial de transposição que troca linhas por colunas produz também um view 
da imagem, sem necessidade de cópia:
End of explanation
"""


a = np.arange(24).reshape((4,6))
print('a:\n',a)
av = a.ravel()
print('av.shape:',av.shape)
print('av:\n',av)
print('np.may_share_memory(a,av):',np.may_share_memory(a,av))

"""
Explanation: Ravel
Aplicando-se o método ravel() a um ndarray, gera-se um view do raster
linearizado (i.e. uma única dimensão) do ndarray.
End of explanation
"""


b = a.copy()
c = np.array(a, copy=True)

print("id de a = ",id(a))
print("id de b = ",id(b))
print("id de c = ",id(c))
print('np.may_share_memory(a,b):',np.may_share_memory(a,b))
print('np.may_share_memory(a,c):',np.may_share_memory(a,c))

"""
Explanation: Cópia profunda
Cria uma copia completa do array, do seu shape e conteúdo. A recomendação é utilizar a 
função copy() para realizar a copia profunda, entretanto é possível conseguir a
copia profunda pelo np.array.
End of explanation
"""


