0

I wrote this simple function to take an array of strings and eliminate duplicates, but it reverts with supplied parameters ["a","b","c"]. I thought it might have something to do with the equals functions (compares two string), but that runs fine on its own. In the debugger, it hangs on the assignment statement shown below, which comes prior to the equals function call.but not clear why.

Any thoughts appreciated.

pragma solidity >=0.7.0 <0.9.0;
contract Tester  {
    
    function dedupeKeys(string[] memory keys) public pure returns(string[] memory) {
       
        string[] memory deduped;
         if (keys.length == 0) return deduped;  
        bool found;
        string memory key;
        key = keys[0];
        deduped[0] = key;   //REMIX DEBUGGER HANGS HERE.            
        for(uint i=1; i<keys.length; i++) {
            found = false;
            key = keys[i];
            for(uint j=0; j<keys.length; j++) {
                if(equal(deduped[j], key)) {
                   found=true; 
                }
            }
            if (!found) {
                deduped[deduped.length]=key;
            }
        }
        return deduped;
    }
    
    function equal(string memory _base, string memory _value)
        internal
        pure
        returns (bool) {
        bytes memory _baseBytes = bytes(_base);
        bytes memory _valueBytes = bytes(_value);

        if (_baseBytes.length != _valueBytes.length) {
            return false;
        }

        for (uint i = 0; i < _baseBytes.length; i++) {
            if (_baseBytes[i] != _valueBytes[i]) {
                return false;
            }
        }

        return true;
    }

}
2
  • 1
    I haven’t used arrays for a long time, but are u sure that is the way to define an array and init it? The other thing I remember string arrays where not allowed , are they allowed now and can they be handled as normal array or 2 dimensional arrays :/
    – Majd TL
    Aug 28, 2021 at 18:56
  • Thanks. I checked the docs, and looks like dynamic arrays are only allowed in storage. If memory, you have to declare them fixed size. (You'd think the compiler would catch that sort of error)
    – GGizmos
    Aug 28, 2021 at 19:35

2 Answers 2

1

I think I found the issue. The problem here is mainly with declaring your dynamic array in memory rather than storage. As it will be a storage array then it should be declared outside your function.

Secondly, your second iteration should consider deduped.length since this is the array your are iterating over

for(uint j=0; j<keys.length; j++)

Lastly, for adding elements to dynamic array you should use array.push(thing). This version of code is working for me.

pragma solidity >=0.7.0 <0.9.0;
contract Tester  {

string[] public deduped;
function dedupeKeys(string[] memory keys) public returns(string[] memory) {
   
    while(deduped.length > 0){
        deduped.pop();
    }
     if (keys.length == 0) return deduped;  
    bool found;
    string memory key;
    key = keys[0];
    deduped.push(key);   //REMIX DEBUGGER HANGS HERE.            
    for(uint i=1; i<keys.length; i++) {
        found = false;
        key = keys[i];
        for(uint j=0; j<deduped.length; j++) {
            if(equal(deduped[j], key)) {
               found=true; 
            }
        }
        if (!found) {
            deduped.push(key);
        }
    }
    return deduped;
}

function equal(string memory _base, string memory _value)
    internal
    pure
    returns (bool) {
    bytes memory _baseBytes = bytes(_base);
    bytes memory _valueBytes = bytes(_value);

    if (_baseBytes.length != _valueBytes.length) {
        return false;
    }

    for (uint i = 0; i < _baseBytes.length; i++) {
        if (_baseBytes[i] != _valueBytes[i]) {
            return false;
        }
    }
            return true;
}

}

3
  • Thanks for this. I thought of putting deduped outside of the function so it could be a dynamic array instead of fixed size, but the problem is then that deduped can't be pure or view. You actually have to change state of the contract, in order to clear deduped, which means all the nodes have to run it to run this function. In several cases this contract is used to retrieve and not set state vars, which is free for pure functions, but incurs gas costs if state is changed.
    – GGizmos
    Aug 29, 2021 at 1:46
  • 1
    If you want to keep the function pure, then you can try using a fixed length array. You just need to define the length at initialisation with something like: string[] memory deduped = new string[](keys.length); but then you will need to some function to clean up the empty array elements but it should work without the need of changing state.
    – Bigbrotha
    Aug 29, 2021 at 9:48
  • Yep thanks that's exactly what I did in the answer above.
    – GGizmos
    Aug 29, 2021 at 15:35
0

Between the two answers above I was able adapt to solve the problem. Below is one solution for a "pure" string array deduper function. Without being able to use mappings and dynamic arrays there's a lot of iterating arrays necessary, but if it's used for display purposes, rather than updating in state, it won't incur gas costs.

function dedupeKeys(string[] memory keyset) public pure returns(string[] memory) {
        require( keyset.length> 0, "dedupeKeys: keys array is empty");
        //indexSet is list of indexes for unique keys.  
        //indexSet[0] is [0]th of keys by definition.
        string[] memory uniqueKeys = new string[](keyset.length);
        uint uniqueKeysCount = 1;
        uniqueKeys[0]=keyset[0];
        for (uint i=1;i<uniqueKeys.length; i++) {
            if (!isMember(uniqueKeys, keyset[i])) {
                uniqueKeys[uniqueKeysCount]=keyset[i];
                uniqueKeysCount++;
            } 
        }
        //get rid of the empty indexes
        return shrinkStringArray(uniqueKeys);
    }
    
  
    function isMember(string[] memory ar, string memory member) private pure returns(bool) {
        for(uint i = 0; i<ar.length; i++) {
            if (ar[i].equals(member)) {
                return true;
            }
        }
        return false;
    }
    
     function shrinkStringArray(string[] memory ar) public pure returns(string[] memory) {
        uint arLength = ar.length;
        string[] memory nonblank = new string[](arLength);
        uint nonBlankCount = 0;
        for(uint i; i<ar.length; i++) {
            if (!ar[i].isEmpty()) {
                nonblank[nonBlankCount]=ar[i];
                nonBlankCount++;
            }
        }
         
        string[] memory retval = new string[](nonBlankCount);
        for(uint i=0; i<nonBlankCount; i++) {
            retval[i]=nonblank[i];
        }
        return retval;
    }
    

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.