As you know, mapping
s 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];
}
}