[Ethereum] Efficient approach to delete element from array in Solidity

arraysgasmappingsoliditystruct

For each user, I want to keep an array of holded assets (each asset has an ID).

My solution up until now is :

struct User {
        uint userId;
        uint[] assets;
    }

For every asset the user holds I push to the user's array the ID of the asset.

I want to give the user an option to delete an asset.

What would be the most efficient approach to this?

Allocating all of the available assets for every user (would be very wasterful given you have a lot of assets available) VS. iterating over all of his assets everytime he'd like to delete an asset, finding it in the array, then deleting it from it and shifting all of the array accordingly – also, the code for this is kinda hideous :

function deleteAsset(uint assetId) external returns(uint) {
        bool assetFound = false;
        // Check if the asset found is the last asset (or we go out of boundaries)
        if (allUsers[msg.sender].assets[allUsers[msg.sender].assets.length - 1] == assetId){
            assetFound = true;
        }

        else{
            // Iterate over all user assets and find its index
            for (uint i = 0; i < allUsers[msg.sender].assets.length - 1; i++) {
                if (!assetFound && allUsers[msg.sender].assets[i] == assetId)
                    assetFound = true;

                if(assetFound)
                    allUsers[msg.sender].assets[i] = allUsers[msg.sender].assets[i + 1];
            }
        }

        if (assetFound){
            delete allUsers[msg.sender].assets[allUsers[msg.sender].assets.length - 1];
            allUsers[msg.sender].assets.length--;
        }
    }

Would be a lot easier if I could save a mapping for each user indicating what asset does he have, but you can't return a mapping from a function and I don't know the benchmarks of view functions and "brute-forcing" all of the assets available for each user can take a plenty of time I assume.

Best Answer

Source: https://github.com/su-squares/ethereum-contract/blob/master/contracts/SuNFT.sol

Here you go:

Algorithm:

uint[] assets;
mapping(uint=>uint) indexOfAsset;

function removeAssetFromArray(uint _assetToDelete) {
  uint index = indexOfAsset[_assetToDelete];
  if (!index) return;

  if (assets.length > 1) {
    assets[index] = assets[assets.length-1];
  }
  assets.length--; // Implicitly recovers gas from last element storage
}

Caveat: this approach assumes an ordered set, not an array. In other words, it assumes the array has no duplicate items.

Related Topic