0

I am looking for a construct that's something like "return an list of key value pairs," which I think would be implemented as an array of mappings. Is that right?

That is, I'd like a data structure that contains a list of (key,value) that I will use as a global state linking some structs to association with another.

What I mean is something like:

// so I have place structs
struct Place {
  uint8 placeid;
  string nameOfPlace;
  ...etc
}

// and a place can contain no more than 1 banner.    
// banners have all been to a place, but they aren't necessarily
// in a place right now. They may have been replaced with another 
// banner.
struct Banner {
  uint8 bannerid;
  uint8 placeAt; // this must match a placeid
  string thingTheBannerSays;
  uint8 timestamp;
}

Place[] places;                        // list all places
mapping(uint8 => Place)  placesById    // given an id, return the place
Banner[] banners;
mapping(uint8 => Banner)  bannersById  // given an id, return the place

// what's missing for me is a data structure
// with global state of [(place0,bannerN),(place1,bannerM)] 
// for all places.  This is what I'm looking for.

function listBannersAtPlacesRightNow() returns (???? arrayOfMappings) {
  ??? mappings[] = arrayOfMappings
  for(uint8 i =0; i < places.length; i++) {
     maxTime[i] = 0;       // declare this correctly
     idWhereMaxTime[i] = 0; // gross, would rather be null
     for(uint8 j=0; j < banners.length; j++) {
        if(banners[j].timestamp > maxTime[i]) {
          maxTime[i] = banners[j].timestamp // high water ;
          idWhereMaxTime[i] = banners[j];
        }
       }
     arrayOfMappings[i] = (places[i], idWheremaxTime[i]);
   }
 return arrayOfMappings;
 }

Sorry, I'm brand new to this, but if this unclear, in words, I'd like some variable to maintain the global state that says "I am a place, I can have 0 or 1 banners. Here is a list of all the places and their current banners. Banners can be tied to exactly one place and never re-used."

Ugly quasi-python that might clarify ugly quasi solidity this would be something like:

places = [{"placeid": 0, "nameofplace": "home"}, {"placeid":1, "nameofplace": "center"}]

banners = [
{"bannerid": 0, "placeAt": 0, "thingthebannersays": "hello!"},
{"bannerid": 1, "placeAt": 0, "thingthebannersays": "hello again from home!"},
{"bannerid": 2, "placeAt": 0, "thingthebannersays": "this replaces 1!"},
{"bannerid": 3, "placeAt": 1, "thingthebannersays": "this is at center!"}]

bannerAtPlaces = [(0,2), #place 0 has current banner 2
                  (1,3)  #place 1 has banner 3
]

def setBannerAtPlaces(placeid, bannerid):
  # this updates bannerAtPlaces according to some rules
  # such as the most-recent timestamp given in the solidity
  # section

# BUT If I wanted to see which places contained which banners, I'd just
# print(bannerAtPlaces).  This is the call I'm looking to repeat
# in solidity.

This is all coming up because I'd rather have the struct for Place contain a field for bannerid, but unfortunately, I want Place to be able to have 0 banners, and it looks like I cannot initialize a null for that---I must default to bannerid = 0. But bannerid==0 is an actual bannerid. It cannot be in two places at once, it's already somewhere else, and cannot be the default for the banner at all new places.

The reason for a list specifying global states would be for rendering this in the browser easily, as a gallery showing all (Place, Banner) pairs.

I am now seeing a similar question here.

1

I wasn't quite sure about the cardinality from your description. I went with "a banner can only be in one place at a time and a place either has or doesn't have exactly 1 banner."

The for loops are serious trouble in Solidity. We need to organize data structures so we can complete everything at a consistent gas cost at any scale - O(1). See this: https://blog.b9lab.com/getting-loopy-with-solidity-1d51794622ad

I don't think you need one-to-many joins, but in case you do have a look at this for ideas: https://medium.com/@robhitchens/enforcing-referential-integrity-in-ethereum-smart-contracts-a9ab1427ff42

That link sort of throws you in the deep end. It links to the CRUD series that explains the patterns I used to bang out this quick sketch:

pragma solidity 0.4.25;

contract Banners {

    struct BannerStruct {
        string marque;
        bytes32 placeId; // 0 or 1 placeId
        uint idPointer;
    }

    struct PlaceStruct {
        string name;
        bytes32 bannerId; // 0 or 1 bannerId
        uint idPointer;
    }

    mapping(bytes32 => BannerStruct) public bannerStructs;
    mapping(bytes32 => PlaceStruct) public placeStructs;

    bytes32[] public bannerIdList;
    bytes32[] public placeIdList;

    function getBannerCount() public view returns(uint) {
        return bannerIdList.length;
    }

    function getPlaceCount() public view returns(uint) {
        return placeIdList.length;
    }

    function isBanner(bytes32 bannerId) public view returns(bool) {
        if(bannerIdList.length==0) return false; // there aren't any, so false
        return bannerIdList[bannerStructs[bannerId].idPointer] == bannerId;
    }

    function isPlace(bytes32 placeId) public view returns(bool) {
        if(placeIdList.length==0) return false;
        return placeIdList[placeStructs[placeId].idPointer] == placeId;
    }

    function placeHasBanner(bytes32 placeId) public view returns(bool) {
        return isBanner(placeStructs[placeId].bannerId);
    }

    function newBanner(bytes32 bannerId, string marque) public returns(bool) {
        require(!isBanner(bannerId)); // no dups
        bannerStructs[bannerId].marque = marque;
        bannerStructs[bannerId].idPointer = bannerIdList.push(bannerId) - 1;
        return true;
    }

    function newPlace(bytes32 placeId, string name) public returns(bool) {
        require(!isPlace(placeId));
        placeStructs[placeId].name = name;
        placeStructs[placeId].idPointer = placeIdList.push(placeId) - 1;
        return true;
    }

    function setPlaceBanner(bytes32 placeId, bytes32 bannerId) public returns(bool) {
        require(isPlace(placeId));
        require(isBanner(bannerId));
        if(bannerStructs[bannerId].placeId != 0) { // moving it. A banner can only be in one place???
            bytes32 oldPlace = bannerStructs[bannerId].placeId;
            placeStructs[oldPlace].bannerId = bytes32(0);
        }
        bannerStructs[bannerId].placeId = placeId;
        placeStructs[placeId].bannerId = bannerId;
    }
}

As you press on, you'll also find that you probably want to reduce the amount of meta-data stored on-chain. I left my structs sparse for this reason. https://blog.b9lab.com/the-joy-of-minimalism-in-smart-contract-design-2303010c8b09

Hope it helps.

  • This is an very thorough answer and quite helpful as I start out. Thank you. – Mittenchops Nov 15 '18 at 18:44
  • Your links are also super helpful. Regarding cardinality, I was shooting for "a banner MUST only be in one place at a time and a place either has or doesn't have exactly 1 banner." So the idea that a banner always has 1 place, but a place has 0 or 1 banners (say there is only 1 flagpole, but a large banner shed next to it). I can work from your tips and links to there, though. Thank you! – Mittenchops Nov 15 '18 at 19:16
  • 1
    I wondered about that. The example is pretty close but it doesn't enforce the rule that every banner must have a place. Certainly doable with a few more tweaks. – Rob Hitchens - B9lab Nov 15 '18 at 20:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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