Posted in relation to the blog initiative.
Simple and appropriate data organization can challenge Solidity newcomers. It wants us to organize everything in ways many of us aren’t accustomed to. Here are some simple and useful patterns in increasing order of capability.
Simple List using Array
Strengths
- In the order of declaration (chronological)
- Provides a count (rows)
- Random access by Row Number (not ID)
Weaknesses
- No random access by Id
- No assurance of uniqueness.
- No check for duplicates.
- Uncontrolled growth of the list
contract simpleList { struct EntityStruct { address entityAddress; uint entityData; // more fields } EntityStruct[] public entityStructs; function newEntity(address entityAddress, uint entityData) public returns(uint rowNumber) { EntityStruct memory newEntity; newEntity.entityAddress = entityAddress; newEntity.entityData = entityData; return entityStructs.push(newEntity)-1; } function getEntityCount() public constant returns(uint entityCount) { return entityStructs.length; } }
Mapping with Struct
Strengths
- Random Access by unique Id
- Assurance of Id uniqueness
- Enclose complex arrays, mappings, structs within each “record”
Weaknesses
- Unable to enumerate the keys
- Unable to count the keys
- Needs a manual check to distinguish a default from an explicit record
contract mappingWithStruct {
struct EntityStruct { uint entityData; bool isEntity; } mapping (address => EntityStruct) public entityStructs; function isEntity(address entityAddress) public constant returns(bool isIndeed) { return entityStructs[entityAddress].isEntity; } function newEntity(address entityAddress, uint entityData) public returns(bool success) { if(isEntity(entityAddress)) throw; entityStructs[entityAddress].entityData = entityData; entityStructs[entityAddress].isEntity = true; return true; } function deleteEntity(address entityAddress) public returns(bool success) { if(!isEntity(entityAddress)) throw; entityStructs[entityAddress].isEntity = false; return true; } function updateEntity(address entityAddress, uint entityData) public returns(bool success) { if(!isEntity(entityAddress)) throw; entityStructs[entityAddress].entityData = entityData; return true; } }
Array of Structs with Unique Ids
Strengths
- Random Access by row number
- Assurance of Id uniqueness
- Enclose complex arrays, mappings and structs within each “record”
Weaknesses
- No random access by Id
- Uncontrolled growth of the list
contract arrayWithUniqueIds { struct EntityStruct { address entityAddress; uint entityData; } EntityStruct[] public entityStructs; mapping(address => bool) knownEntity; function isEntity(address entityAddress) public constant returns(bool isIndeed) { return knownEntity[entityAddress]; } function getEntityCount() public constant returns(uint entityCount) { return entityStructs.length; } function newEntity(address entityAddress, uint entityData) public returns(uint rowNumber) { if(isEntity(entityAddress)) throw; EntityStruct newEntity; newEntity.entityAddress = entityAddress; newEntity.entityData = entityData; knownEntity[entityAddress] = true; return entityStructs.push(newEntity) - 1; } function updateEntity(uint rowNumber, address entityAddress, uint entityData) public returns(bool success) { if(!isEntity(entityAddress)) throw; if(entityStructs[rowNumber].entityAddress != entityAddress) throw; entityStructs[rowNumber].entityData = entityData; return true; } }
Mapped Structs with Delete-enabled Index
Strengths
- Random Access by unique Id or row number
- Assurance of Id Uniqueness
- Enclose complex arrays, mappings and structs within each “record”
- Count the records/Ids
- Enumerate the Ids
- Ability to logically control the size of the active list
Weaknesses
- Marginally increased code complexity
- Higher storage cost than alternatives
- Key list is inherently unordered
Detailed description: https://medium.com/@robhitchens/solidity-crud-part-1-824ffa69509a#.20k69nqrc
contract mappedWithUnorderedIndexAndDelete {
struct EntityStruct {
uint entityData;
uint listPointer;
}
mapping(address => EntityStruct) public entityStructs;
address[] public entityList;
function isEntity(address entityAddress) public constant returns(bool isIndeed) {
if(entityList.length == 0) return false;
return (entityList[entityStructs[entityAddress].listPointer] == entityAddress);
}
function getEntityCount() public constant returns(uint entityCount) {
return entityList.length;
}
function newEntity(address entityAddress, uint entityData) public returns(bool success) {
if(isEntity(entityAddress)) throw;
entityStructs[entityAddress].entityData = entityData;
entityStructs[entityAddress].listPointer = entityList.push(entityAddress) - 1;
return true;
}
function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
if(!isEntity(entityAddress)) throw;
entityStructs[entityAddress].entityData = entityData;
return true;
}
function deleteEntity(address entityAddress) public returns(bool success) {
if(!isEntity(entityAddress)) throw;
uint rowToDelete = entityStructs[entityAddress].listPointer;
address keyToMove = entityList[entityList.length-1];
entityList[rowToDelete] = keyToMove;
entityStructs[keyToMove].listPointer = rowToDelete;
entityList.length--;
return true;
}
}
There are certainly more ways of combining structs, arrays and mappings. Do you have favorite or interesting pattern? Show us how it works!