1

Suppose I have a struct as follows:

struct Item {
    uint groupId;
    uint itemId;
    // more data here
}

Each groupId is unique. Each itemId is unique only to the given group. So, each Item is only uniquely defined by the combination (tuple) of groupId and itemId.

Now I want to have a mapping that lets me do a couple of things:

  1. I want to retrieve an item based on groupId and itemId.
  2. I want to get all items for a given groupId.

If in my contract, i have this:

mapping(uint => (mapping(uint => Item)) items

then I can easily get a given Item:

item = items[groupId][itemId];

But how can I get a list/array of all items for a given groupId?

2 Answers 2

0

Instead of a nested mapping, you could nest a storage array inside a mapping as follows:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

contract MappingsC {
    struct Item {
        uint groupId;
        uint itemId;
    }
    
    mapping(uint => Item[]) private items;
    
    function setItem(uint groupId, uint itemId) public {
        items[groupId].push(Item(groupId, itemId));
    }

    function getItem(uint groupId, uint itemId) public view returns (Item memory) {
        return items[groupId][itemId];
    }
    
    function getItems(uint groupId) public view returns (Item[] memory) {
        return items[groupId];
    }

}

That way, you could return each item individually by passing in a groupId and a itemId to getItem(...), and still be able to return an entire nested storage array just by passing in a groupId to getItems(...). This works as arrays are iterable, unlike mappings.

1
  • I don't think this solution works, because in the getItem function you are trying to get an item by its itemId that is in an array, so you are using the itemId as an index in the array. So, if there is only one item in that array, and it has an itemId = 5, and you try to call the getItem function with itemId 5, using 5 as an index will revert because there is no element at index 5 of the array. Aug 14, 2022 at 16:27
0

As you know, mappings are not iterable. But you could implement something similar to an iterable mapping, as shown here: https://solidity-by-example.org/app/iterable-mapping/

Alternatively, you could create a mapping of an array of items groupid => Item[] and loop through that array whenever you need to get a specific item by its id:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

contract IterableMappingAlternative {

    struct Item {
        uint groupId;
        uint itemId;
    }
    
    mapping(uint => Item[]) private items;
    
    function setItem(uint groupId, uint itemId) public {
        items[groupId].push(Item(groupId, itemId));
    }

    function getItem(uint groupId, uint itemId) public view returns (Item memory) {
        Item[] storage itemList = items[groupId];
        // This is not efficient.
        for(uint i = 0; i < itemList.length; i++) {
            Item storage item = itemList[i];
            if(item.itemId == itemId) {
                return item;
            }
        }
    }
    
    function getItems(uint groupId) public view returns (Item[] memory) {
        return items[groupId];
    }

}

But this is not efficient.

A better approach would be:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

contract IterableMappingAlternative {

    struct Item {
        uint groupId;
        uint itemId;
    }
    
    mapping(uint => Item[]) private items;
    mapping(uint => uint) private itemIndexMapping;
    
    function setItem(uint groupId, uint itemId) public {
        Item[] storage itemList = items[groupId];
        itemList.push(Item(groupId, itemId));
        // Save the index of the item that was just added. Since `push` puts the element at the end of the array,
        // then its index is at itemList.length - 1
        itemIndexMapping[itemId] = itemList.length - 1;
    }

    function getItem(uint groupId, uint itemId) public view returns (Item memory) {
        // Get the itemIndex from the itemIndexMapping based on the itemId.
        // This way, we use it to access the item in the `items` mapping in constant time O(1)
        uint itemIndex = itemIndexMapping[itemId];
        return items[groupId][itemIndex];
    }
    
    function getItemsByGroup(uint groupId) public view returns (Item[] memory) {
        return items[groupId];
    }

}

With this approach, we can access the items individually by their itemId in constant time O(1). This is an efficient solution.

Alternatively, you can set the Solidity version to the latest ^0.8.16 and use custom types to indicate which is the itemId and which the ItemIndex in the itemIndexMapping to avoid confusion:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.16;

type ItemId is uint;
type ItemIndex is uint;

contract IterableMappingAlternative {

    struct Item {
        uint groupId;
        uint itemId;
    }
    
    mapping(uint => Item[]) private items;
    mapping(ItemId => ItemIndex) private itemIndexMapping;
    
    function setItem(uint groupId, uint itemId) public {
        Item[] storage itemList = items[groupId];
        itemList.push(Item(groupId, itemId));
        // Save the index of the item that was just added. Since `push` puts the element at the end of the array,
        // then its index is at itemList.length - 1
        itemIndexMapping[ItemId.wrap(itemId)] = ItemIndex.wrap(itemList.length - 1);
    }

    function getItem(uint groupId, uint itemId) public view returns (Item memory) {
        // Get the itemIndex from the itemIndexMapping based on the itemId.
        // This way, we use it to access the item in the `items` mapping in constant time O(1)
        uint itemIndex = ItemIndex.unwrap(itemIndexMapping[ItemId.wrap(itemId)]);
        return items[groupId][itemIndex];
    }
    
    function getItemsByGroup(uint groupId) public view returns (Item[] memory) {
        return items[groupId];
    }

}

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.