Solidity: Returns filtered array of structs without 'push' - solidity

I have this contract with an array of structs:
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
contract Tickets {
struct Ticket {
uint id;
int val;
}
Ticket[] tickets;
function addTicket(uint id, int val) public returns(bool success) {
Ticket memory newTicket;
newTicket.id = id;
newTicket.val = val;
tickets.push(newTicket);
return true;
}
function getTicket(uint id) public view returns(Ticket memory) {
uint index;
for(uint i = 0; i<tickets.length; i++){
if (tickets[i].id == id) {
index = i;
break;
}
}
Ticket memory t = tickets[index];
return t;
}
function findTickets(int val) public view returns(Ticket[] memory) {
Ticket[] memory result;
for(uint i = 0; i<tickets.length; i++){
if (tickets[i].val == val) {
result.push(tickets[i]); // HERE IS THE ERROR
}
}
return result;
}
}
I need to returns a filtered by val array but when I buil this code: result.push(tickets[i].id); it throw this error:
TypeError: Member "push" is not available in struct Tickets.Ticket memory[] memory outside of storage.
How I can implement the filter without using push ?

Returning dynamic-length array of struct is still a bit tricky in Solidity (even in the current 0.8 version). So I made a little workaround to make it work even in the 0.6.
Determine the result count, you'll need it for step 2.
Create a fixed-length array
Fill the fixed-length array
Return the fixed-length array
function findTickets(int val) public view returns(Ticket[] memory) {
uint256 resultCount;
for (uint i = 0; i < tickets.length; i++) {
if (tickets[i].val == val) {
resultCount++; // step 1 - determine the result count
}
}
Ticket[] memory result = new Ticket[](resultCount); // step 2 - create the fixed-length array
uint256 j;
for (uint i = 0; i < tickets.length; i++) {
if (tickets[i].val == val) {
result[j] = tickets[i]; // step 3 - fill the array
j++;
}
}
return result; // step 4 - return
}

Related

How can I avoid looping through this array twice?

My code is as follows:
/**
*
* #notice Returns a Sale array with all the sales that have not ended.
*
*/
function getOngoingSales() public view returns(Sale[] memory) {
uint256 _ongoingSalesCounter = 0;
for(uint i = 0; i<sales.length; i++) {
if (sales[i].ended == false) _ongoingSalesCounter++;
}
Sale[] memory _ongoingSales = new Sale[](_ongoingSalesCounter);
uint256 _pos = 0;
for(uint i = 0; i<sales.length; i++) {
if (sales[i].ended == false) {
_ongoingSales[_pos] = sales[i];
_pos ++;
}
}
return _ongoingSales;
}
The problem is that I have to loop twice the array to get to my wanted result. Is there a more effective way of doing this?
You can try to combine the two loops into one,
/**
*
* #notice Returns a Sale array with all the sales that have not ended.
*
*/
function getOngoingSales() public view returns(Sale[] memory) {
Sale[] memory _ongoingSales;
for(uint i = 0; i<sales.length; i++) {
if (sales[i].ended == false) _ongoingSales.push(sales[i]);
}
return _ongoingSales;
}
Before time complexity: O(2n)
After time complexity: O(n)

Push method for an array of structures

contract task_list {
struct tasksStruct {
string name;
uint32 timestamp;
bool is_done;
}
tasksStruct[] tasks;
function add_task(string _name) public {
tasksStruct newTask = tasksStruct(_name, now, false);
tasks.push(newTask);
}
function show_opened_tasks() public view returns (uint) {
uint count_of_opened_tasks = 0;
for (uint i=0; i<tasks.length; i++){
if (!tasks[i].is_done) {
count_of_opened_tasks += 1;
}
}
}
}
how does pushing for an array of structures work here? and why there is an error out of range of the array?
You're missing the data location for the reference types in the add_task() function. After fixing this, you'll be able to push to the array.
// added location `memory` for `name`
function add_task(string memory _name) public {
// added location `memory` for `newTask`
tasksStruct memory newTask = tasksStruct(_name, uint32(now), false);
tasks.push(newTask);
}
Also you probably want to return the count_of_opened_tasks from the show_opened_tasks() function - otherwise you'll always get returned the default value 0.
function show_opened_tasks() public view returns (uint) {
uint count_of_opened_tasks = 0;
for (uint i=0; i<tasks.length; i++){
if (!tasks[i].is_done) {
count_of_opened_tasks += 1;
}
}
// added return
return count_of_opened_tasks;
}

Try to return an array in a for loop

I am very new to Solidity, so sorry for the silly question. I am trying to add an element of a struct and then return it, here is my code:
struct Flat {
uint256 priceInWei;
address currentOccupant;
bool flatIsAvailable;
}
Flat[8] public flatDB;
modifier landlordOnly() {
require(msg.sender == landlordAddress);
_;
}
constructor() public {
landlordAddress = msg.sender;
for (uint i=0; i<8; i++) {
flatDB[i].flatIsAvailable = true;
if (i % 2 == 0) {
flatDB[i].priceInWei = 0.1 ether;
} else {
flatDB[i].priceInWei = 0.2 ether;
}
}
}
uint256[] array;
function getFlatDB() payable public returns (uint256) {
for (uint i=0; i<8; i++) {
array.push(flatDB[i].priceInWei);
}
return array;
}
But when I try to compile I have an error at line 41:
TypeError: Return argument type uint256[] storage ref is not implicitly convertible to expected type (type of first return variable) uint256. return array; ^---^
Can someone explain me what's wrong? Thanks!
Seems like the return type you have (uint256) doesn't correspond to the type you're actually trying to return (uint256[]). Try rewriting your getFlatDB function in the following way:
function getFlatDB() payable public returns (uint256[]) {
uint256[] memory array = new uint256[](8);
for (uint i=0; i<8; i++) {
array[i] = flatDB[i].priceInWei;
}
return array;
}
Note how we declare the temporary fixed size return array inside the function with memory keyword.

Solidity smart contract for lottery, but I can only give the prize for the first one. I want it to give it to all winners

Can't make it gives the prize for all winners, just the first one person that putted the right combination. I think there is a problem with findWinners function but I can't find it. If I make address A the good combination, then B with a not good one, and finally C with also a good one, it only gives the prize to A.
pragma solidity ^0.5.1;
contract Lottery {
Game[] ListGames;
// uint public nbGames = ListGames.length;
uint price = 100000;
uint[6] winnerGame;
// winnerGame = [1,2,3,4,5,6];
address payable[] ListWinners;
uint winnersPayed = 0;
uint expiredGames = 0;
struct Game {
address payable gamer;
uint[6] numbers;
uint date;
bool valid;
}
function setWinner(uint[6] memory _winnerGame) public {
winnerGame = _winnerGame;
}
function findWinners() public returns (address payable[] memory) {
uint size = ListGames.length;
bool win = true;
for (uint j = 0; j < size; j++) {
for (uint i=0; i<6 ; i++) {
if ((ListGames[j].numbers[i] != winnerGame[i]) || !ListGames[j].valid) {
win = false;
}
}
if (win) {
ListGames[j].valid = false;
expiredGames ++;
ListWinners.push(ListGames[j].gamer);
}
}
return ListWinners;
}
function payer() public returns (uint) {
uint nbWinners = ListWinners.length;
uint prize;
if (nbWinners - winnersPayed != 0) {
prize = address(this).balance / (nbWinners- winnersPayed);
for (uint i = 0; i < nbWinners; i++) {
if(ListWinners[i] != address(0)) {
ListWinners[i].transfer(prize);
delete ListWinners[i];
winnersPayed++;
require(ListWinners[i] == address(0));
}
}
}
return nbWinners;
}
modifier costs(uint amount) {
require(msg.value >= amount);
_;
}
function postGame(uint[6] memory _numbers) public payable costs(price) {
Game memory newGame = Game(msg.sender, _numbers, block. timestamp, true);
ListGames.push(newGame);
}
function numberValidGames() public view returns (uint) {
return ListGames.length - expiredGames;
}
function getWinnersList() public view returns (address payable [] memory) {
return ListWinners;
}
function getTime() public view returns (uint) {
return block.timestamp;
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}

Add element to struct

I can't use ( push ) because it is used with state variable only
This is the error :
Error message
Is there any alternatives for ( push )
contract m{
struct Message{
address sender;
address receiver;
uint msgContent;
} // end struct
Message[] all;
function get ( address from ) internal
returns ( Message[] subMsgs){
for ( uint i=0; i<all.length ; i++)
{
if ( all[i].sender == from )
{
subMsgs.push (all[i]);
}
}
return subMsgs;
}
} // end contract
You can only use push on dynamic-size arrays (i.e. storage arrays), and not fixed-size arrays (i.e. memory arrays) (see Solidity array documentation for more info).
So to achieve what you want, you'll need to create a memory array with a desired size and assign each element one by one. Here is the example code:
contract m {
struct Message{
address sender;
address receiver;
uint msgContent;
} // end struct
Message[] all;
function get(address from) internal returns (Message[]) {
// Note: this might create an array that has more elements than needed
// if you want to optimize this, you'll need to loop through the `all` array
// to find out how many elements from the sender, and then construct the subMsg
// with the correct size
Message[] memory subMsgs = new Message[](all.length);
uint count = 0;
for (uint i=0; i<all.length ; i++)
{
if (all[i].sender == from)
{
subMsgs[count] = all[i];
count++;
}
}
return subMsgs;
}
} // end contract