'Is there an efficient way to join an array of strings into a single string in Solidity?

My goal is to write a function that takes an array of strings, and returns a single string containing all of the input strings combined.

For example, in Python this could be done this way:

result = ''.join(['hello', 'solidity', 'world'])  # hellosolidityworld

My current implementation doesn't seem efficient at all:

function concat(string[] memory words) internal pure returns (string memory) {
    // calculate output length
    uint256 bytesLength;
    for (uint256 i = 0; i < words.length; i++) {
        bytesLength += bytes(words[i]).length;
    }

    bytes memory output = new bytes(bytesLength);
    uint256 currentByte;

    for (uint256 i = 0; i < words.length; i++) {
        bytes memory bytesWord = bytes(words[i]);
        for (uint256 j = 0; j < bytesWord.length; j++) {
            output[currentByte] = bytesWord[j];
            currentByte++;
        }
    }

    return string(output);
}

Is there a better way to do this?



Solution 1:[1]

You can simplify the function by using abi.encodePacked() that combines multiple values and returns a dynamic-length byte array (type bytes).

Since string in Solidity is effectively stored the same way as a byte array, you can then easily convert the output byte array to a string.

pragma solidity ^0.8;

contract MyContract {
    function concat(string[] calldata words) external pure returns (string memory) {
        bytes memory output;

        for (uint256 i = 0; i < words.length; i++) {
            output = abi.encodePacked(output, words[i]);
        }

        return string(output);
    }
}

Try with ["hello", " ", "world"] for example.

Solution 2:[2]

I have just published an assembly implementation of javascript like Array.join() functions working for both reference types (bytes[] and string[]) and value types (bytes2[], uint16[], etc.). The lib is here and benchmarking it against a for loop with string.concat shows that it's up to 5x more efficient (depending on the number of strings and their length).

So using the above mentioned packages, it's like:

import {Array} from "@clemlaflemme.eth/contracts/lib/utils/Array.sol";


contract MyContract {

   using Array for string[];

   function foo(string[] memory arr) public pure returns (string memory) {
       return arr.join();
       // also return arr.join(","); for example
   }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Petr Hejda
Solution 2 ClementWalter