Mapping e Arrays

porMatheusem10/05/2022

Nesse artigo iremos abordar como funcionam os mapping e arrays no Solidity, bem como, quando utilizá-los em seu contrato inteligente.

Mapping (Mapas)

Os mapas são utilizados para guardar um valor X em uma chave Y. São definidos com a sintaxe mapping(keyType => valueType)

  • keyType
    • pode ser definido por qualquer tipo de valor interno, bytes, string, address ou qualquer contrato ou tipo de dado disponível no Solidity.
  • valueType
    • pode ser qualquer tipo de valor, podendo ser até mesmo outro mapa ou matriz.

Os mapas não podem ser iteráveis.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Mapping {
// Mapeamento de endereço para uint
mapping(address => uint) public myMap;
function get(address _addr) public view returns (uint) {
// O mapeamento sempre retorna um valor
// Se o valor do mapa nunca foi definido,
// ele irá retornar o valor padrão para o tipo de variável atribuindo
return myMap[_addr];
}
function set(address _addr, uint _i) public {
// Atualiza o valor do mapa para o endereço informado
myMap[_addr] = _i;
}
function remove(address _addr) public {
// Deleta o valor existente para o endereço informado
delete myMap[_addr];
}
}
contract NestedMapping {
// Mapeamento aninhado (mapeamento de endereço para outro mapeamento)
mapping(address => mapping(uint => bool)) public nested;
function get(address _addr1, uint _i) public view returns (bool) {
// Você pode obter valores de um mapeamento aninhado
// mesmo quando não é inicializado
return nested[_addr1][_i];
}
function set(
address _addr1,
uint _i,
bool _boo
) public {
nested[_addr1][_i] = _boo;
}
function remove(address _addr1, uint _i) public {
delete nested[_addr1][_i];
}
}

Uma das aplicações onde o mapping pode ser utilizado, por exemplo, em um jogo onde você precisa armazenar a pontuação de um determinado jogador

Vamos ver como ficaria esse tipo de aplicação

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract GameScore {
// Definimos nosso mapa como playerScores que irá
// receber o endereço do jogador e a pontuação
mapping(address => uint) public playerScores;
function getScore(address _addr) public view returns (uint) {
// Iremos retornar o score atual do jogador
return playerScores[_addr];
}
function setScore(address _addr, uint _score) public {
// Atualiza o score do jogador
playerScores[_addr] = _score;
}
}

Array (Matrizes)

Diferente dos mapas, as matrizes podem armazenar um valor único em uma lista de tipo único, podendo ser definida para qualquer tipo, address, uint, string ou qualquer outro disponível. Uma array pode ser definida com um tamanho único definido na compilação ou um tamanho dinâmico.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Array {
// Maneiras para definir uma array
uint[] public arr; // Array de tamanho dinâmico
// Array de tamanho dinâmico inicializada com os valores [1, 2, 3]
uint[] public arr2 = [1, 2, 3];
// Array de tamanho fixo, todos os elementos inicializam em 0
uint[10] public myFixedSizeArr;
function get(uint i) public view returns (uint) {
return arr[i];
}
// Solidity pode retornar o array inteiro
// Mas esta função deve ser evitada para arrays
// que podem crescer indefinidamente em tamanho
function getArr() public view returns (uint[] memory) {
return arr;
}
function push(uint i) public {
// Adiciona um valor na matriz
// Isso aumentará o tamanho da matriz em 1
arr.push(i);
}
function pop() public {
// Remove o último valor adicionado
// Isso diminuirá o tamanho da matriz em 1
arr.pop();
}
function getLength() public view returns (uint) {
// Retorna o tamanho da matriz
return arr.length;
}
function remove(uint index) public {
// Excluir o valor de um índice não irá diminuir o valor da matriz.
// Irá definir o valor no índice para o valor padrão do tipo,
// neste caso, o valor será setado para 0
delete arr[index];
}
function examples() external {
// Cria uma nova matriz na memória
// Somente matrizes com tamanho fixo podem ser criadas assim
uint[] memory a = new uint[](5);
}
}

Exemplos de remoção de elemento da matriz

Remova um elemento da matriz deslocando os elementos da direita para a esquerda

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract ArrayRemoveByShifting {
// [1, 2, 3] -- remove(1) --> [1, 3, 3] --> [1, 3]
// [1, 2, 3, 4, 5, 6] -- remove(2) --> [1, 2, 4, 5, 6, 6] --> [1, 2, 4, 5, 6]
// [1, 2, 3, 4, 5, 6] -- remove(0) --> [2, 3, 4, 5, 6, 6] --> [2, 3, 4, 5, 6]
// [1] -- remove(0) --> [1] --> []
uint[] public arr;
function remove(uint _index) public {
// Verifica se o índice passado existe na matriz,
// se não, ele retorna uma mensagem de erro
require(_index < arr.length, "índice fora do limite");
for (uint i = _index; i < arr.length - 1; i++) {
arr[i] = arr[i + 1];
}
arr.pop();
}
function test() external {
arr = [1, 2, 3, 4, 5];
remove(2);
// [1, 2, 4, 5]
assert(arr[0] == 1);
assert(arr[1] == 2);
assert(arr[2] == 4);
assert(arr[3] == 5);
assert(arr.length == 4);
arr = [1];
remove(0);
// []
assert(arr.length == 0);
}
}

Substitua um elemento da matriz copiando o último elemento para o índice que será removido

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract ArrayReplaceFromEnd {
uint[] public arr;
// A remoção de um elemento da matriz cria uma lacuna vazia.
// Uma maneira de manter o array compacto é
// mover o último elemento para o índice que foi removido.
function remove(uint index) public {
// Move o último elemento para o índice que foi removido
arr[index] = arr[arr.length - 1];
// Remove o último elemento
arr.pop();
}
function test() public {
arr = [1, 2, 3, 4];
remove(1);
// [1, 4, 3]
assert(arr.length == 3);
assert(arr[0] == 1);
assert(arr[1] == 4);
assert(arr[2] == 3);
remove(2);
// [1, 4]
assert(arr.length == 2);
assert(arr[0] == 1);
assert(arr[1] == 4);
}
}

Nesse artigo, apresentamos várias maneiras de utilizar o mapeamento de variáveis com o mapping e o armazenamento de dados com o array


Testar no Remix