0

I am trying to do something that I believe it should be simple enough. I want to make a getter function iterating a struct array find the min value and the values that is equal to this minvalue of a property and store the addresses coresponding to this value on another array. The code I am using is as follows.

pragma solidity ^0.5.1;

contract arraytest{

struct Player {
    uint estimation;
    // address player // maybe
    // other elements
}

Player[] public playersArr;
address[] public playersaddress;

    function setbid(uint _prediction, address _playeraddr) public{
    Player memory player;
    player.estimation = _prediction;
    playersArr.push(player);
    playersaddress.push(_playeraddr);

}

    function getWinnersArray() public view returns (address[] memory) {
    uint i=0;


    uint counter = 0;
    uint j=1;
    uint minvalue=playersArr[i].estimation;

    address[] memory winners = new address[](j);
    winners[counter] = playersaddress[i];
    counter++;


    for (i = 1; i < playersArr.length; i++) {



        if (playersArr[i].estimation < minvalue) {

            minvalue=playersArr[i].estimation;
            delete winners;

            winners = new address[](j);
            counter=0;

            winners[counter] = playersaddress[i];
            counter++;
        }else if(playersArr[i].estimation == minvalue){

            //append to winners array
        }
    }
    return (winners);
}

I tried several ways but I couldnt achieve my goal. Should i use mapping somehow. Appreciate any help.

  • "I want to make a getter function... and store..." - a getter (view) function cannot store anything! – goodvibration Sep 2 at 13:48
  • I guess OP wanted to say that he wants to return new array, not to store it in the state. @dos007, is it correct? Also, what exactly is the problem? you can't compile the code, or function return value is not the same as expected? Please provide more information – Oleg Kondrakhanov Sep 2 at 15:39
  • Yes I want to return an array of winners. It is compiling. I have trouble with the array handling since is memory and I have to create new array every time I want to add an ellement since the memory cannot change lenght. So this makes the append ellement not possible. Is the proper way to store to the state and then have a caller? I want to use the winners array for transfer eth to winners(ok this is irrelevant at the time). – dos007 Sep 2 at 16:21
0

It seems like several challenges.

First, establish the lowest number in the set. One way would be to watch this as the numbers are added to avoid iterating the whole set to find the smallest number.

Second, discover all predictions that match the lowest number.

A less obvious but important challenge is to accomplish it all without iteration. Generally, arrays and loops should be used with great caution or the contract won't scale due to gas cost. https://blog.b9lab.com/getting-loopy-with-solidity-1d51794622ad

A solution to this problem is provide clients with a series of steps, each with O(1) complexity and rely on clients themselves to make multiple calls to assemble something that would otherwise be O(n). Reducing everything to O(1) usually means carefully organizing storage so all needed answers are available in one simple step.

A linked list or other simple data structure might do the trick. Here, I used a binary search tree that just happens to group all the ties conveniently.

pragma solidity 0.5.1;

import "./HitchensOrderTree.sol";

contract MinValues {

    using HitchensOrderStatisticsTreeLib for HitchensOrderStatisticsTreeLib.Tree;
    HitchensOrderStatisticsTreeLib.Tree playerPredictions; // sorted key=>value pair, playerAddress=>estimate 

    // allows multiple predictions per player, but players can make the same prediction only once

    function setBid(address player, uint prediction) public {
        playerPredictions.insert(addressToKey(player), prediction);
    }

    // the lowest value in the set

    function min() public view returns(uint) {
        return playerPredictions.first();
    }

    // the count of predictions equal to the lowest prediction

    function minKeyCount() public view returns(uint value, uint count) {
        uint lowest = min();

        // safe to ignore the warning. The unused variables should be tidied up for a polished project 

        (uint _parent, uint _left, uint _right, bool _red, uint _keyCount, uint _count) = playerPredictions.getNode(lowest);
        return (playerPredictions.first(), _keyCount);
    }

    // the address of a player tied with the lowest prediction. Request one row of minKeyCount() at a time.

    function playerMinAtRow(uint row) public view returns(address) {
        uint lowest = min();
        return keyToAddress(playerPredictions.valueKeyAtIndex(lowest, row));
    }

    // type conversions 

    function addressToKey(address a) internal pure returns(bytes32) {
        return bytes32(int(int160(a)));
    }

    function keyToAddress(bytes32 b) internal pure returns(address) {
        return address(uint160(uint(b)));
    }       
}

A client can insert key/value pairs (playerAddress/predictions), then:

  1. Get the lowest value from min()
  2. Get the count of player addresses tied for the minimum prediction with minKeyCount()
  3. Get the player addresses tied for the minimum with for(i=0, i<minKeyCount(), i++) { contract.playerMinAtRow(i) ...build an array client-side }

In case it isn't clear, every step has a predictable maximum gas cost at any scale so each step will complete. This is better than an unpredictable gas cost that increases as the arrays get larger.

Read about the binary search tree here: https://hackernoon.com/binary-search-trees-and-order-statistics-for-ethereum-db47e2dd2c36

Hope it helps.

  • Really sorry for the omission but this was part of my logic of the contract. I needed the array cause the min value is the difference of the prediction from a value given at a certain point of time. Also The prediction can be lower from the given value so an if condition should be set. Can I implement this logic to the script you provided. Step 3 - Then I should transfer the winning amounts to the winners from a client loop to the method which pays the winners from a pool contract?Should I make another post giving all the info. Appreciate the reply. – dos007 Sep 3 at 14:38
  • The value is given from an API every three days after players put their predictions so I should for clarity reasons put in in the smart contract? It will be enough claritywise and gas cost to store at every insert the predictions-addresses to the blockchain and localy the math client side. Thanks again. – dos007 Sep 3 at 16:41
  • It will be a little trickier to find the closest prediction but it is possible with rank(), previous(), next(), etc. You would want to check for exact match, then, failing that, pinpoint the closest above and below, then work out which is the closest. Once you have a winning prediction, you can count the players who share it and enumerate their addresses. You should probably use a withdrawal pattern for claiming prizes. – Rob Hitchens - B9lab Sep 3 at 16:46
  • Ok so implementing your script with the addition of the "closest prediction" with rank(), previous(),next(), etc instead of min() - lowest but folowing the same logic - functions. correct? Thanks a LOT for your help!! – dos007 Sep 3 at 18:32
  • That's the idea. It won't be especially easy but yes, and a b-tree is better than a linked list in this case because you will need zero in on a value somewhere in the middle of the sort. The library can do that sort of thing. – Rob Hitchens - B9lab Sep 3 at 21:07

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.